20201207のdockerに関する記事は15件です。

【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:        fec3683

Docker で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へアクセスすると
下記のようにアクセスログが発生する。

スクリーンショット 2020-12-07 23.25.16.png

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 run

 yutakaf@mi  ~/Desktop/git  docker container run --publish 80:80 --detach nginx
1920f9a96bb38d5cefe54f81dddb40ccc918dff9b7a7dea40c132fa986762ea6

コンテナの一覧を表示 ls

docker container 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

コンテナを指定して停止

docker container stop

 yutakaf@mi  ~/Desktop/git  docker container stop 1920f9a96bb3
1920f9a96bb3
 yutakaf@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_moore

STATUSがExitedになっていることがわかる。

コンテナに名前をつける

--name コンテナに名前を割り当てます
docker container run

 yutakaf@mi  ~/Desktop/git  docker container run --publish 80:80 --detach --name webhost nginx
4ee13d2807b491d3d8306bb0b3e43003b7e6916fc39d87e281c671e688faa595
 yutakaf@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   webhost

NAMESにwebhostと名前がついた。

コンテナのログを取得する。

docker container logs

 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

コンテナ消去

docker container rm

 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

一通り使えるようになったらチートシートを作ろうと思う。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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

手順

  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: {}
    
  2. docker-compose upで起動することを確認

  3. http://localhost:8000 へアクセス

  4. 言語設定やユーザー設定などの初期登録を済ませる

  5. 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
        ...
    
  6. docker-compose upで起動することを確認

  7. https://localhost へアクセス

総評

WIP

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[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

手順

  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: {}
    
  2. docker-compose upで起動することを確認

  3. http://localhost:8000 へアクセス

  4. 言語設定やユーザー設定などの初期登録を済ませる

  5. 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
        ...
    
  6. docker-compose upで起動することを確認

  7. https://localhost へアクセス

総評

WIP

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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.ini

2.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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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行で書けますが非常に長いので、実際に利用する時は関数を用意してエイリアスを登録して利用しています。

~/.zshrc
alias 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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行で書けますが非常に長いので、実際に利用する時は関数を用意してエイリアスを登録して利用しています。

~/.zshrc
alias 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoでユニットテストを書く方法

こんにちは Masuyama です。
今回は Django でユニットテストを書く方法をお伝えしたいと思います。
Django の環境構築から丁寧に解説し、Django を使ったことがない人でもユニットテストを書けるようになるはずです。

ここでは記事を投稿するようなブログアプリをベースとして、ユニットテストを書く方法をお伝えしていきます。

テスト用 Django アプリの作成

まずは土台となるアプリケーションを作成していきます。

環境構築

作業用のディレクトリを作成します。
ここでの名前はなんでもよいですが、とりあえず blog としておきます。

mkdir blog
cd blog

pipenv のインストール

ベースとなる環境は汚さない方が他のモジュールなどの影響を排除できるため、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.txt
Django==3.1.0

※今回は Django 3.1.0 で作っていきますが Django 2.2 あたりでも問題なく動くとは思います

モジュールのインストール

下記のコマンドを実行することで、requirements.txt に基づきモジュールをインストールします。
ここでは Django だけがインストールされるはずです。

pipenv install -r requirements.txt

Django プロジェクトの作成

blog ディレクトリ直下で実行

django-admin startproject mysite

親プロジェクト直下でのファイル構成はこのようになっているかと思います。

├── Pipfile
├── Pipfile.lock
├── mysite
│   ├── manage.py
│   └── mysite
│       ├── __init__.py
│       ├── settings.py
│       ├── urls.py
│       └── wsgi.py
└── requirements.txt

Django サーバのテスト起動

開発用サーバを起動する機能がついています。
(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 を押下します。

image.png

これがすべての始まりです。この画面が出ていれば、最初のステップは完了しています!おめでとうございます。

プロジェクトの設定

さて、先ほどテストサーバへアクセスした時に英語での表記になっていましたね。
こういった設定は 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'

もう一度テストサーバへアクセスすると、日本語表記になっていることが分かるかと思います。
(また、この画面からでは確認できませんがタイムゾーンも東京設定になっています)

image.png

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.py

blog 配下に様々なファイルが作成されていることが分かるかと思います。

ここで、このアプリが作成されたことをプロジェクトに教えてあげる必要があります。

mysite/setting.py の中に "INSTALLED_APPS" という欄がありますので、その中で blog アプリの存在を教えてあげましょう。

mysite/settings.py
INSTALLED_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.py
from 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.py
from 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 ディレクトリ内で下記コマンドを実行してもよいでしょう。

/blog
touch urls.py

blog 配下はこのようなファイル構成です。

.
├── __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.py
from 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 の中身が表示されれば成功です。
image.png

model の準備

実際に記事を登録するための準備をします。

ブログで記事を管理するためには model を作成します。
model はデータベースと Django の橋渡しの役割を持っており、これのおかげで我々は SQL といったデータベース構文を意識することなくデータベースにデータを登録することができます。

最初に設定する models.py では、どのようなデータを登録していくのかを定義します。
Excelの表でいうと、表の各カラムのカラム名を定義したり、各カラムに入るデータがどのようなもの(文字列や数値など)を定義するところです。

今回はブログアプリであり、記事 (Post) を修正していくので Post モデルを作成します。
タイトル、本文、日付が入ればとりあえず十分です。

blog/models.py
from 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.html

Django アプリ作成の中でこのファイルを直接いじることはありませんが、中身はこのようになっており、これのおかげでカラムの作成などを 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 migrate

view の準備

管理サイトで作成した記事の一覧を表示できるようにしていきます。

template 準備(ファイル作成)

最初に template を作成しましょう。
templates/blog 配下に post_list.html を作成します。

└── templates
    └── blog
        ├── index.html
        └── post_list.html

view ファイルの修正

Django ではクラスベース汎用ビュー という仕組みを使うと、簡単に model を引っ張ってきて記事を表示させたり、
テンプレートを表示させたりすることが出来、アプリ作成をグンと効率的に行うことができるようになります。
ちなみに前々回、views.py をいじったときに generic という表記がありました。
これもクラスベース汎用ビューを使う準備として必要な宣言です。

view.py
from django.views.generic import TemplateView

汎用クラスビューには様々な種類があり、その中でも単純に template を表示させるためだけに使われるのが
index.html の表示に使っていた TemplateView です。
index.html の表示のために、呼び出す template を views.py の中で指定していたことになります。

view.py
class 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.py
from 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.py
from django.test import TestCase
from blog.models import Post

そしてテストクラスを作っていくのですが、必ず TestCase を継承したクラスにします。

test_models.py
from django.test import TestCase
from blog.models import Post

class PostModelTests(TestCase):

さて、この PostModelTest クラスの中にテストメソッドを書いていきます。
TestCase を継承したクラスの中で「test」で始めることで、
Django がそれはテストメソッドであることを自動で認識してくれます。
そのため、def の後は必ず test で始まるメソッド名を名付けましょう。

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)

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.py
from 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.py
from 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.py
from 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.py
from 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」で始めるように前述しましたがsetUptearDownというメソッドが存在します。

setUpメソッドではテストケース内で使うデータの登録をし、
tearDownメソッドでは setUp メソッド内で登録したデータの削除を行えます。
(どちらも、どんなデータを登録するかは明示的に記述する必要があることには注意しましょう)

同じテストケースの中で何回もデータの登録をするような処理を書くのは手間&テストに時間がかかる要因になるので、
共通する処理は一箇所にまとめてしまおうというものです。

これらのメソッドを使い、test_views.py を作成するとこのようになります。

test_views.py
from 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 独自のテスト方法を用いたテストで冗長的にチェックする方法もありますが
いまは使いまわしでもよいので処理を増やす時にテストを作成する癖をつけ、後々のチェックの手間を省くようにしていきましょう。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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 upgrade

dockerのインストール
 【Dockerコンテナ内のUbuntuではsystemctlは使えない】の手順1を参考に。

docker-composeのインストール

$ sudo apt install -y docker-compose 

問題なくインストールできているか確認

$ docker-compose --version
docker-compose version 1.25.0, build unknown

2.コンテナ構築用のDockerfile作成(2つ)

Dockerfile格納用のディレクト作成

$ sudo mkdir ./con1
$ sudo mkdir ./con2

Dockerfileの作成

コンテナ1のDockerfile作成

$ sudo nano ./con1/Dockerfile
Dockerfile
# ベースイメージの取得
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/Dockerfile
Dockerfile
# ベースイメージの取得
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.yml
docker-compose.yml
version: '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:8888

4.操作確認

全サービスの起動

$ docker-compose up

 ※初回起動時は、ビルドやイメージのダウンロードが自動で開始される。

以下のURLにアクセスして、認証なしでnotebookが開くか確認する。
  http://[VMの外部IP(グローバルIPアドレス)]:8080/
  http://[VMの外部IP(グローバルIPアドレス)]:8081/
 

試しにport:8080で開かれるnotebookでimportコマンドを実行してみると、./con1/Dockerfileでパッケージインストールされていないものはできないはず。

また、作成したファイルはホスト側のディレクトリをマウントしていて、そこをjupyter noteobookの初期ディレクトリに設定しているため、作成ファイルはホスト側上で永続化されるはず。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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作成


  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

既に進行中のコンソールセッションがあるため、リモートコンピューター上の他のコンソールセッションに接続できませんでした。

事象 : 作ったコンテナにリモートデスクトップ接続できない

  • 環境
    • Windows10 Pro 64bit バージョン1909
    • Docker Desktop 2.4.0.0
      • Docker version 19.03.13
      • docker-compose version 1.27.4

image.png

原因 : 自分のPCの同じポートに接続しようとしているから

以下の記事を見て「はっ!」とした・・・Windows on Dockerを使って自分のPCにコンテナ作っているんだから同じポート指定したら意味ないじゃん!
これまで、サーバ上にDockerコンテナを作って使っていたので気が付かなかった・・・。

繋がらないIPアドレスをレジストリエディタで検索してみたところ
なんだ、自分も接続先と同じ固定IPアドレスを持っていたのか
自分自身に対してリモート接続を行おうとしていたためだった。
リモートデスクトップ接続で - プログラまあのネタ帳

version: "3.7"
services:
  hoge:
...省略...
    ports:
      - "8080:8080" # アプリケーションサーバ用
      - "3389:3389" # リモートデスクトップ用

対応 : ポートを変える

自分のPC内同士で接続するのだから別々のポートを指定しなければおかしくなる。
よく忘れるがポートは- "{ホストのポート}:{コンテナのポート}"

    ports:
      - "18080:8080"
      - "13389:3389"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby on Rails(v6対応)をDockerで環境構築しHerokuにデプロイする

本記事の対象者

Railsは未経験でしたが、とりあえず学習環境つくろうと思い、備忘録のため記事化しました。
アプリケーションの開発に集中するため、Docker環境をコピペで作りたい人向けの記事です。
Rails6を対象としています。

自身のスキルレベル

  • Railsは全くの未経験(なんにも知りません)
  • Dockerはそこそこやったことあります
  • 他フレームワークでWebの基本的な開発はやったことあります

必要なファイル

環境構築に必要なファイルは以下の通りです。
myappディレクトリを作って、以下のファイルを作成してください。

myapp
- Dockerfile
- Gemfile
- Gemfile.lock
- entrypoint.sh
- docker-compose.yml

1. 環境構築

1-1. Dockerfile

Dockerfile
FROM 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.yml
version: '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:
      - db

1-3. Gemfile

Rails用のパッケージファイルだそうです。
バージョン6を指定しています。

Gemfile
source '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 yarn

Rails6から必須のwebpackerのインストールを行なっておきます。

$ docker-compose run web bundle exec rails webpacker:install

コンテナ起動とDBの作成を行います。

$ docker-compose up
$ docker-compose run web rake db:create

ここまで成功したらRailsが起動していることを確認します。
同時に、ローカル環境にRailsの環境一式がインストールされていることを確認してください。

http://localhost:3000

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 open

4. アプリを開発

ここまでできたら、あとはアプリを開発のみです。
お疲れ様でした。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Alpineでバージョンを指定してPythonをいれる

Dockerでflask & vueのイメージをビルドしたかったときに詰まった.

Dockerfile
RUN apk update && apk add --no-cache python3 && python3 --version
# 3.8.6

2020/12/07時点は自動的に3.8.6がインストールされた.しかし,PipでPytorchをいれるときにエラーが出てしまったのでPythonのバージョンを指定することに

また,apkでインストールされるバージョンはこちらから確認できそう

バージョンを指定してPythonをいれる

Dockerfile
RUN apk update && apk add --no-chache --repository http://dl-cdn.alpinelinux.org/alpine/v3.10/main python3~=3.7

リポジトリとバージョンを指定するとできた.
リポジトリのブランチとPythonのバージョンは先ほどのサイトで確認できる.上の例でいうところのv3.10python3~=3.7

参考文献

https://tamakiii.hatenablog.com/entry/2020/02/11/191548

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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のベースが出来上がりました。これをもとに追加の設定を入れ込んでみたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Dockerを使ってPostgreSQL+Goの開発環境を作ってみた

はじめに

GoからPostgreSQLに接続する部分がよくわかってなかったので、自分でDocker環境作って試してみた。

参考

ここを大いに参考にさせてもらった。
Go PostgreSQLにつないでみる - Qiita
Docker で作る postgres 環境 | Crudzoo

Docker

ポイントはnetworks部分を同じにしてるところ。

ネットワークの準備

# docker network create postgres-test-network
# docker network ls
NETWORK ID          NAME                    DRIVER              SCOPE
xxxxxxxxxxxx        postgres-test-network   bridge              local

PostgreSQL

こんな感じ。
POSTGRES_HOST_AUTH_METHOD: trueは推奨してないって言われたけど、ローカルのお試し環境だから無視した。

Dockerfile
FROM postgres:11-alpine
ENV LANG ja_JP.utf8
docker-compose.yml
version: '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 -d

Go

Dockerfile
FROM golang:latest
RUN mkdir /go/src/work
WORKDIR /go/src/work
ADD . /go/src/work
docker-compose.yml
version: '3'
services:
  app:
    build: .
    tty: true
    volumes:
      - .:/go/src/work
networks:
  default:
    external:
      name: postgres-test-network

これで立ち上げる

docker-compose up -d

アクセス部分

Goの方のコンテナの中では、これを実行する。

ここをほぼほぼコピペさせてもらった。
Go PostgreSQLにつないでみる - Qiita

main.go
package 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.go

Posticoで確認

一応、DBの中身もGUIツールで確認した。良い感じ。
Postico – a modern PostgreSQL client for the Mac
image.png

Screen Shot 2020-12-07 at 8.41.10.png

おわりに

ローカルでに動く環境作れたので、いろいろ試してみたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む