20200404のdockerに関する記事は7件です。

nvidia-container-toolkitでKivyを動かした

困ったこと

Dockerでnvidia/cudaのイメージを使用して、Kivyを入れたら以下のエラーが出てしまいます。
どうもlibGL関係のエラーっぽいです。
解決策を探していたのですが、nvidiaから提供されているnvidia/cudaglのイメージを使ったほうが楽そうだったので、
それで解決しました。
似たようなエラーがROSで起こった気もするのでこっちのイメージを使うほうがいいかもしれません。

root@:/work/test_kivy# python3 main.py 
[INFO   ] [Logger      ] Record log in /root/.kivy/logs/kivy_20-04-04_4.txt
[INFO   ] [Kivy        ] v1.11.1
[INFO   ] [Kivy        ] Installed at "/usr/local/lib/python3.5/dist-packages/kivy/__init__.py"
[INFO   ] [Python      ] v3.5.2 (default, Oct  8 2019, 13:06:37) 
[GCC 5.4.0 20160609]
[INFO   ] [Python      ] Interpreter at "/usr/bin/python3"
[INFO   ] [Factory     ] 184 symbols loaded
[INFO   ] [ImageLoaderFFPy] Using ffpyplayer 4.3.1
[INFO   ] [Image       ] Providers: img_tex, img_dds, img_sdl2, img_ffpyplayer, img_gif (img_pil ignored)
[INFO   ] [Window      ] Provider: sdl2(['window_egl_rpi'] ignored)
libGL error: No matching fbConfigs or visuals found
libGL error: failed to load driver: swrast
[INFO   ] [Window      ] Provider: x11(['window_egl_rpi', 'window_sdl2'] ignored)
libGL error: No matching fbConfigs or visuals found
libGL error: failed to load driver: swrast

Dockerfile

kivyのインストールを含めたDockerfileをメモとして残して起きます。

Dockerfile
FROM nvidia/cudagl:10.1-devel-ubuntu16.04

ENV NVIDIA_VISIBLE_DEVICES ${NVIDIA_VISIBLE_DEVICES:-all}
ENV NVIDIA_DRIVER_CAPABILITIES ${NVIDIA_DRIVER_CAPABILITIES:+$NVIDIA_DRIVER_CAPABILITIES,}graphics

# ここは自分の好きなツールを入れています
RUN apt-get update && apt-get -y upgrade && apt-get install -y wget unzip tree git vim byobu && \
    rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*

RUN apt-get update && apt-get install -y build-essential zlib1g-dev libssl-dev libffi-dev &&\
    apt-get install -y build-essential checkinstall libreadline-gplv2-dev && \
    rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*

RUN apt-get update && apt-get install -y libncursesw5-dev libssl-dev libsqlite3-dev &&\
    apt-get install -y tk-dev libgdbm-dev libc6-dev libbz2-dev mesa-utils libgl1-mesa-glx cmake &&\
    rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*

RUN apt-get update && apt-get install -y libsdl2-dev &&\
    rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*

RUN mkdir /workspace
RUN cd /workspace && wget https://www.python.org/ftp/python/3.7.3/Python-3.7.3.tgz &&\
    tar zxvf Python-3.7.3.tgz &&\
    cd  /workspace/Python-3.7.3 &&\
    ./configure --enable-optimizations --enable-shared CFLAGS=-fPIC &&\
    make -j8 && make install && ldconfig
RUN python3 -m pip install kivy
RUN python3 -m pip install kivy_examples
RUN python3 -m pip install ffpyplayer
WORKDIR /workspace
RUN rm Python-3.7.3.tgz

build & run

$ docker build . -t kivy_test:latest
$ xhost +
$ docker run -it --gpus all --net host -e DISPLAY=$DISPLAY -v /tmp/.X11-unix:/tmp/.X11-unix  kivy_test:latest

あとは、Tutorials(翻訳済み) » Pong Game Tutorial(翻訳済み)に載っているサンプルを動かして動作確認しました。
cupy,chainer(GPU)が動くことはサンプルで確認しました。

参考サイト

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

DockerでReactの開発環境を作る

初投稿です。春から大学生の雑魚です。人生ハードモード。
色々不備があると思いますけど、優しくしてね???(?コワイヨォーーーー)

Dockerとは

日本語のドキュメントによると

Docker とは、開発者やシステム管理者が、アプリケーションの開発、移動、実行するためのプラットフォームです。

「チーム内で同じ開発環境で開発する事を助けるアプリケーション」って感じです。

用語

コンテナ

アプリケーションの実行を行う開発環境のこと。イメージの情報を元に構築される。

イメージ

コンテナの元となる。コンテナ内の情報が保存されている。

Dockerfile

イメージの内容を記述するファイル。イメージをの元となる。ビルドを行うと、Dockerfileに記述した情報を元にイメージが作成される。

まとめると

  1. Dockerfileを記述
  2. ビルドを実行しイメージを作成
  3. イメージをもとにコンテナを実行
  4. コンテナ内でアプリケーションを実行

こんな感じになると思います。

実際に書いてみる

Dockerのインストール

テキトーにやってください!???

Dockerで実行するアプリの構築

初めに、コンテナ内で実行したアプリをローカルで作成します。

terminal
$ npx create-react-app docker-practice
$ cd docker-react-example
$ yarn start

こんな画面が出たらOK
docker-react.png

Dockerfileを書く

コンテナの内容を書いていきます。

terminal
$ touch Dockerfile
Dockerfile
# ベースイメージの作成
FROM node:12.16.1
# コンテナ内で作業するディレクトリを指定
WORKDIR /usr/src/app
# package.jsonとyarn.lockを/usr/src/appにコピー
COPY ["package.json", "yarn.lock", "./"]
# パッケージをインストール
RUN yarn install
# ファイルを全部作業用ディレクトリにコピー
COPY . .
# コンテナを起動する際に実行されるコマンド
ENTRYPOINT [ "yarn", "start" ]

各種コマンド解説

FROM

Dockerfileには必須のコマンド。
ベースとなるイメージをDocker Hubから取って来る。
今回はnodeの12.16.1を取って来ている。

WORKDIR

コンテナ内の作業用ディレクトリを指定する。rootとusr内には色々とデフォルトでファイルがあるから、そこは避けたほうがいいみたい。

COPY

ローカルにあるファイルをコンテナ内にコピーする
構文は
1. COPY <ソース>... <送信先>
2. COPY ["<ソース>",... "<送信先>"]
のどちらか

RUN

ビルド時にだけ実行されるコマンド(?)。
初回だけで実行したいコマンドを書くと?

ENTRYPOINT

コンテナの起動時に実行される。
似たようなコマンドにCMDというのがあるが、ここで説明すると万里の長城長くなるので割愛

なんでpackage.jsonとyarn.lockを先にコピーしてんの?

この記事によると、

dockerはビルド開始時にdocker daemonにDockerfileのディレクトリにあるファイルを全部tarして送る。
これが大きいとtarするのに時間がかかる

みたいなんで、コンテナ内にパッケージの一覧とバージョンの依存関係が書かれたファイルだけコピーして、その後コンテナ内でインストールした方が早いみたいですね!!!

イメージをビルド

コマンドの構文はこんな感じ
→ docker build [オプション] パス

terminal
$ docker build -t test:1.0 ./

-tは名前とタグを指定できる(tagはバージョンみたいなやつ)
指定しなければ名前:latestになる(多分)

また、作成したイメージは

terminal
$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
test                1.0                 335dbb73b720        40 seconds ago      1.22GB
node                12.16.1             d834cbcf2402        5 weeks ago         916MB

で見ることができる。

コンテナを起動

作成したイメージを元にコンテナを起動するで!
コマンドの構文
→ docker run [オプション] イメージ [コマンド] [引数...]

terminal
$ docker run -it --name sample -p 3000:3000 -v $PWD:/usr/src/app test:1.0
terminal
Compiled successfully!

You can now view docker-react-example in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://172.17.0.2:3000

Note that the development build is not optimized.
To create a production build, use yarn build.

こんな画面が出て
http://localhost:3000 にアクセスして
docker-react.png
こんな画面が出たら成功です。

ファイルを編集すると、

src/App.js
import React from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Dockerの勉強やでーwwww
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn Ruby on Rails
        </a>
      </header>
    </div>
  );
}

export default App;

リアルタイムで変更を反映してくれるはずです。
スクリーンショット 2020-04-01 16.32.40.png

各オプション解説

-it

-iと-tっていうオプションを一気に指定している。
-tは疑似ターミナルの割当
-iはアタッチしていなくても STDIN をオープンにし続けているらしい。

-p

コンテナのポートとホストのポートの関連付けを行う
ホスト:コンテナの順番で指定するので、今回の場合はホストの3000がコンテナの3000と接続されている。

-v

ホストのファイルのパス:コンテナのファイルのパスで指定する
volumeの略。ホスト内のファイルをコンテナ内のディレクトリにマウントして、
ホスト上で行ったファイルの変更をあたかもコンテナ上で行ったかのように見せることができる。
これによってファイルの差分を自動が実現されている。

--name

コンテナの名前を指定
今回はsampleと指定している。

--rm(おまけ)

--rmをつけるとコンテナを停止した時に自動で削除してくれる。
これでホストがコンテナだらけになることを防ぐことができる。

起動中のコンテナに入る

docker execで起動中のコンテナに入ることができる。

構文は

terminal
docker exec [オプション] コンテナ コマンド [引数...]
terminal
$ docker exec -it sample bash
root@fe248383642c:/usr/src/app# 
root@fe248383642c:/usr/src/app# ls
Dockerfile  docker-command.md  docker-compose.md  dockerfile.md  node_modules  package.json  public  src  yarn.lock
root@fe248383642c:/usr/src/app# 
# exitで抜けることが出来ます。
root@fe248383642c:/usr/src/app# exit
exit
$

こんな感じでファイルをイジイジすることができます。

起動しているコンテナを一覧で表示

docker ps で出来ます。
-aオプションを付けると停止中のコンテナも出てきます。

terminal
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
fe248383642c        test:1.0            "yarn start"        24 hours ago        Up 13 minutes

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                    PORTS                    NAMES
fe248383642c        test:1.0            "yarn start"           24 hours ago        Up 4 minutes              0.0.0.0:3000->3000/tcp   sample
bac4be724af7        docker-ruby_ruby    "irb"                  39 hours ago        Exited (1) 18 hours ago                            ruby
c2346fe966a2        test                "yarn start example"   2 days ago          Exited (1) 24 
(中略)

コンテナの停止

docker stop コンテナ名 で出来ます。

terminal
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
fe248383642c        test:1.0            "yarn start"        24 hours ago        Up 13 minutes       0.0.0.0:3000->3000/tcp   sample
$ docker stop sample
sample
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
$ 

コンテナの再起動

docker restart コンテナ名 で出来ます。

terminal
$ docker restart sample
sample
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
fe248383642c        test:1.0            "yarn start"        24 hours ago        Up 7 seconds        0.0.0.0:3000->3000/tcp   sample

終わりに

大体よく使うコマンドとかについて触れられたかなと思います。
Dockerの内部構造はあまり理解できてないので、別の記事でアウトプットしたいなーって感じです。

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

[Docker] webコンテナからdbコンテナへの接続方法

docker-compose.yml
:docker-compose.yml
version: '3'
services:
  db:
    container_name: local-mysql
    image: mysql:5.7
    volumes:
      - ./db/mysql_data:/var/lib/mysql
    environment:
      MYSQL_DATABASE: 'local-mysql-db'
      MYSQL_PASSWORD: password
      MYSQL_USER: root
    ports:
      - "3306:3306"
    networks:
      - app-net

  web:
    container_name: "local-application"
    build: .
    command: rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/fishingshares
    ports:
      - "3000:3000"
    depends_on:
      - db
    links:
      - db
    networks:
      - app-net

networks:
  app-net:
    driver: bridge
$ mysql -h local-mysql -u root -D local-mysql-db -p

詳しく解説すると、
mysql -h [host名] -u [ユーザ名] -D [database名] -p
ということになる。

以上のコマンドをwebコンテナ内で実行すると、mysqlに接続できる。

以上

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

docker-composeで起動したプロセスのUID、GIDをホストユーザと同じにする

dockerは本当に便利で、何でもdockerで開発をしています。
でもちょっと不満があるとすれば、コンテナの中のユーザと、ホスト側で実行したユーザのUID/GIDが異なるため、
コンテナ内のユーザがvolumeマウントして生成するファイルが、ホスト側のユーザパーミッションで書き換えられない
というのが不満でした。

どう解決するのか

この環境はプロダクション環境で使うのではなく、あくまで開発環境で使用することを想定しています。
nodeのオフィシャルイメージだと最初からnodeユーザが登録されているので、
コンテナ内で何かしら動かす際にnodeユーザで実行することを想定しています。
悩んだ挙げ句、とりあえず落ち着いたのは以下。

ホストユーザのUID、GIDを環境変数としてコンテナに渡す

.envファイルを作成し、以下のような情報を記述。
記述する際にはシェルスクリプトを準備して作成。

make_env.sh
#!/bin/bash
set -eu
cat <<EOT > .env
LOCALUID=`id -u`
LOCALGID=`id -g`
EOT

生成されたファイルは以下な感じ。

.env
LOCALUID=1003
LOCALGID=1003

entrypointでUID/GID を変更する

nodeのオフィシャルイメージだと最初からnodeユーザが登録されている。
コンテナ内で何かしら動かす際にnodeユーザで実行する。

entrypoint.sh
#!/bin/bash
set -eu

# 一時的なグループを作成
groupadd -g 11111 tmpgrp

# nodeユーザを一時的なグループに一旦所属させる
usermod -g tmpgrp node

# もともと所属していたnodeグループを削除
groupdel node

# ホストユーザのGIDと同じGIDでnode グループを作成
groupadd -g $LOCALGID node

# nodeユーザのGID をホストユーザのGIDに設定
usermod -g $LOCALGID node

# nodeユーザのUID をホストユーザのUIDに設定
usermod -g $LOCALUID node

# 一時的に作ったグループを削除
groupdel tmpgrp

su node
exec "$@"

結果

以下のようなdocker-compose.yamlを作成して実験。

docker-compose.yaml
version: "3"

services:
  node:
    image: node:lts-buster-slim
    volumes:
      - ./app:/app
      - ./entrypoint.sh:/entrypoint.sh:ro
    entrypoint: /entrypoint.sh
    env_file:
      - ./.env

無事ホストユーザと同じUID/GIDに変更されていることが確認。

$docker-compose run --rm node bash
node@e9aa2121a23f:/$ id
uid=1003(node) gid=1003(node) groups=1003(node)
node@e9aa2121a23f:/$ 

一人で自マシン上で開発しているのならUID/GIDが1000でコンテナ内のUID/GIDも1000で同じだから気にしなくても
良いかなと思うのですが、会社内で共有PC上で開発していると、UID/GIDの1000じゃなくなるのでちょっと困っていました。
これをベースイメージとして社内で使えば、root権限を与えなくてもコンテナが生成したファイルやディレクトリを
ホストユーザが編集・削除できるようになりそうです。

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

PHPがない環境でEmacsでPSR2のflycheckを使用する

Dockerを使って開発している場合、Emacsエディタが動いている環境にPHPがインストールされていない場合もよくあると思います。
EmacsでPSR2のflycheckを使って文法チェックを動かすためには、phpcsコマンドが使える必要があるので、わざわざPHPからインストールするのかという問題が出てきます。

それを避けるために、phpcsのDockerコンテナを使ってEmacsでflycheckを動かす方法を紹介します。
※ Docker等のインストールは完了していることが前提となります。
※ Docker化によるオーバーヘッド等の検証はまだ行っておりません。

環境

Ubuntu: 18.04
Docker: 19.03
Emacs: 25.2

セットアップ

以下の内容のファイルを作成します。

/usr/local/bin/phpcs
#!/bin/bash
docker run -i -v $(pwd):$(pwd) -w=$(pwd) --rm phpqa/phpcs $@

実行権限を与えます。

chmod 755 /usr/local/bin/phpcs

動作確認します。

phpcs --version

これでEmacsが正しく設定されていればflycheckが正しく動作するようになるはずです。

参考

ここでは細かい.emacs側の設定はスキップしますが、参考までにこのようにflycheckを有効化しています。

.emacs
(add-hook 'php-mode-hook
          '(lambda ()
             (setq flycheck-phpcs-standard "PSR2")
             'php-enable-psr2-coding-style
             ))
(add-hook 'after-init-hook #'global-flycheck-mode)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Microsoft] CLIから始めるSQL Server

これはなに?

docker pullしてrunしたはいいけれど、SQL Server Management Studio (通称SSMS)がないゆえに、CLIからSQL Serverを使うことになった男のメモです。

接続

sqlcmd -H ホスト名 -U ログインID -P パスワード

データベースを指定する場合は-d データベース名をつける

例)

sqlcmd -H localhost -U sa -P password2020! -d mydb

SQLファイルを読み込み

sqlcmd -H ホスト名 -U ログインID -P パスワード -i ファイル名

ファイルがいくつもある場合は、こんな感じでどうでしょうか。bash/zsh使いのひと用です。

for a in *.sql; do
  sqlcmd -H ホスト名 -U ログインID -P パスワード -i "$a"
done

xargsのほうがいいかもしれません。

ls -1 *.sql | xargs -I% sqlcmd -H ホスト名 -U ログインID -P パスワード -i %

ファイル名にバージョン番号がついている(例えば、migration-1.0.0.sqlとか)場合は、sort --version-sortで並べ直すと幸せになれます。

(おまけ) Dockerから始めるSQL Server

Macのひとは、Docker Desktopをインストールします。

Docker hubを参照して、好みのバージョンのSQL Serverをpullします。

下記は2017を落とす場合です。

docker pull mcr.microsoft.com/mssql/server:2017-latest

コンテナを起動します。
mssqlという名前をつけました。
カレントディレクトリを/mntにマウントして、コンテナ内から見えるようにしました。

docker run -e 'ACCEPT_EULA=Y' -e 'SA_PASSWORD=password2020!' -p 1433:1433 --name mssql -v "$PWD:/mnt" -d mcr.microsoft.com/mssql/server:2017-latest

sqlcmdコマンドはこのように実行します。
作業ディレクトリを/mntにしています。

docker exec -it -w /mnt mssql /opt/mssql-tools/bin/sqlcmd

(なんでPATH通ってないんや。。。)

(おまけその2) SQLから始めるSQL Serverデータベース

データベースを作ったり、ユーザーを作ったりするSQLは別記事に書きました。

[Microsoft] SQLから始めるSQL Serverデータベース

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

MANスタック(MongoDB, Angular, Nest.js)をdocker-compose一発で起動する

始めに

MANスタックってなに?

JavaScriptでWebアプリを開発する方にとっては、MEANスタック(MongoDB + Express + Angular + Node.js)が馴染み深いのではないでしょうか?

MEANスタックの Node.js + Express を Node.js のフレームワークの Nest.js に置き換えたものがMANスタック(MongoDB + Angular + Nest.js)です!

MANスタック自体の説明等は、Rapid full stack for Angular developers— The MAN Stack (Part I) を参照してください。

MEANスタックに比べてどうなの?

Nest.js は、デフォルトで Expressをwrapしています。MEANスタックで今まで開発していた方であれば、特に苦労することなく乗り換えることができるのではないでしょうか?

また、Angularに非常に影響を受けているフレームワークなので、記法がほとんど一緒です。(特に何もしなくてもTypeScript Onlyな環境で開発できます。)

Angular に影響を受けていると前述しましたが、Spring にもかなり色濃く影響を受けているのではないでしょうか。そういった意味で、Java有識者(Spring有識者)にとっては、開発しやすいフレームワークだと感じています。

本記事終了後にできるようになること

  • docker-compose up -d一発でMANスタックのアプリケーションが起動できる。
  • 本番環境などでそのまま使用することは想定しておらず、あくまで開発時の動作確認を楽にするためと捉えてください。

手順

前提

  • Node.js の開発環境整備済み
  • Angularの開発環境の整備済み
  • Nest.jsの開発環境整備済み
  • Docker/Docker Composeの環境整備済み

環境の確認

以下のコマンドを実行できる場合は、前提となる環境構築が済んでいます。

Node.js
$ node --version
v12.14.1
Angular
$ ng --version

     _                      _                 ____ _     ___
    / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
   / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
  / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
 /_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
                |___/


Angular CLI: 9.0.4
Node: 12.14.1
OS: linux x64

Angular: 
... 
Ivy Workspace: 

Package                      Version
------------------------------------------------------
@angular-devkit/architect    0.900.4
@angular-devkit/core         9.0.4
@angular-devkit/schematics   9.0.4
@schematics/angular          9.0.4
@schematics/update           0.900.4
rxjs                         6.5.3
Nest.js
$ nest --version
6.13.3
Docker/Docker Compose
$ docker --version
Docker version 17.12.1-ce, build 7390fc6
$ docker-compose --version
docker-compose version 1.25.0, build 0a186604

Angular, Nest.jsのアプリケーションのひな型作成

Angular
$ ng new angular-sample
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? CSS
CREATE angular-sample/README.md (1030 bytes)
CREATE angular-sample/.editorconfig (246 bytes)
CREATE angular-sample/.gitignore (631 bytes)
CREATE angular-sample/angular.json (3631 bytes)
CREATE angular-sample/package.json (1291 bytes)
CREATE angular-sample/tsconfig.json (543 bytes)
CREATE angular-sample/tslint.json (1953 bytes)
CREATE angular-sample/browserslist (429 bytes)
CREATE angular-sample/karma.conf.js (1026 bytes)
CREATE angular-sample/tsconfig.app.json (210 bytes)
CREATE angular-sample/tsconfig.spec.json (270 bytes)
CREATE angular-sample/src/favicon.ico (948 bytes)
CREATE angular-sample/src/index.html (299 bytes)
CREATE angular-sample/src/main.ts (372 bytes)
CREATE angular-sample/src/polyfills.ts (2835 bytes)
CREATE angular-sample/src/styles.css (80 bytes)
CREATE angular-sample/src/test.ts (753 bytes)
CREATE angular-sample/src/assets/.gitkeep (0 bytes)
CREATE angular-sample/src/environments/environment.prod.ts (51 bytes)
CREATE angular-sample/src/environments/environment.ts (662 bytes)
CREATE angular-sample/src/app/app-routing.module.ts (246 bytes)
CREATE angular-sample/src/app/app.module.ts (393 bytes)
CREATE angular-sample/src/app/app.component.css (0 bytes)
CREATE angular-sample/src/app/app.component.html (25755 bytes)
CREATE angular-sample/src/app/app.component.spec.ts (1083 bytes)
CREATE angular-sample/src/app/app.component.ts (218 bytes)
CREATE angular-sample/e2e/protractor.conf.js (808 bytes)
CREATE angular-sample/e2e/tsconfig.json (214 bytes)
CREATE angular-sample/e2e/src/app.e2e-spec.ts (647 bytes)
CREATE angular-sample/e2e/src/app.po.ts (301 bytes)
✔ Packages installed successfully.
    Successfully initialized git.
Nest.js
$ nest new nest-sample
⚡  We will scaffold your app in a few seconds..

CREATE /nest-sample/README.md (3370 bytes)
CREATE /nest-sample/nest-cli.json (64 bytes)
CREATE /nest-sample/package.json (1695 bytes)
CREATE /nest-sample/tsconfig.build.json (97 bytes)
CREATE /nest-sample/tsconfig.json (336 bytes)
CREATE /nest-sample/tslint.json (426 bytes)
CREATE /nest-sample/src/app.controller.spec.ts (617 bytes)
CREATE /nest-sample/src/app.controller.ts (274 bytes)
CREATE /nest-sample/src/app.module.ts (249 bytes)
CREATE /nest-sample/src/app.service.ts (142 bytes)
CREATE /nest-sample/src/main.ts (208 bytes)
CREATE /nest-sample/test/app.e2e-spec.ts (630 bytes)
CREATE /nest-sample/test/jest-e2e.json (183 bytes)

? Which package manager would you ❤️  to use? npm
✔ Installation in progress... ☕

?  Successfully created project nest-sample
?  Get started with the following commands:

$ cd nest-sample
$ npm run start


                                Thanks for installing Nest ?
                       Please consider donating to our open collective
                              to help us maintain this package.


                     ?  Donate: https://opencollective.com/nest

動作確認

Angular
$ cd angular-sample
$ npm run start

http://localhost:4200 にアクセスして以下の画面が表示されればOKです。

image-20200330190827002.png

Nest.js
$ cd nest-sample
$ npm run start

適当な REST Clientから以下のリクエストを発行し、レスポンスが返却されればOKです。

リクエスト

GET http://localhost:3000 HTTP/1.1

レスポンス

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: text/html; charset=utf-8
Content-Length: 12
ETag: W/"c-Lve95gjOVATpfV8EL5X4nxwjKHE"
Date: Sun, 29 Mar 2020 20:15:48 GMT
Connection: close

Hello World!

API作成

一応、一連の連携ができることを確かめるためにNest.jsで簡単なAPIを作成します。作成するAPIの仕様は、以下の様。

  • /api/book-list のエンドポイントを持つ

  • MongoDBに事前に登録済みの書籍の一覧を取得し、返却する

  • 最低限、動作することを目指すためログ出力やエラー処理などは実装しない

Nest.js は、フレームワーク本体に CLI ツールが存在するので、それを使用してひな型を作成します。

MongoDBを使用するためにライブラリをインストールする
$ npm install --save @nestjs/mongoose mongoose
$ npm install --save-dev @types/mongoose
接続定義
app.module.ts
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';

@Module({
  // MongoDBコンテナに接続する
  imports: [MongooseModule.forRoot('mongodb://mongo:27017/sample-app')],
})
export class AppModule {}
スキーマ作成
book.schema.ts
import * as mongoose from 'mongoose';

export const BookSchema = new mongoose.Schema({
  name: String,
  author: String,
  detail: String,
});
Module, Controller, Service のひな型を作成
$ nest generate module book
CREATE /src/book/book.module.ts (81 bytes)
UPDATE /src/app.module.ts (308 bytes)
$ nest generate controller book
CREATE /src/book/book.controller.spec.ts (479 bytes)
CREATE /src/book/book.controller.ts (97 bytes)
UPDATE /src/book/book.module.ts (166 bytes)
$ nest generate service book
CREATE /src/book/book.service.spec.ts (446 bytes)
CREATE /src/book/book.service.ts (88 bytes)
UPDATE /src/book/book.module.ts (240 bytes)

nest-sample/src配下が以下の様であれば、OKです。(必ずしも同じ構成とする必要はないです)

src/
|- book
|   |- book.controller.spec.ts
|   |- book.controller.ts
|   |- book.module.ts
|   |- book.service.ts
|- app.controller.spec.ts
|- app.controller.ts
|- app.module.ts
|- app.service.ts
|- main.ts
book.module.ts
import { Module } from '@nestjs/common';
import { BookController } from './book.controller';
import { BookService } from './book.service';
import { MongooseModule } from '@nestjs/mongoose';
import { BookSchema } from 'src/schemas/book.schema';

@Module({
  imports: [MongooseModule.forFeature([{ name: 'Book', schema: BookSchema }])],
  controllers: [BookController],
  providers: [BookService]
})
export class BookModule { }

book.interface.ts
import { Document } from 'mongoose';

export interface Book extends Document {
  name: string;
  author: string;
  remarks: string;
}

book.service.ts
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { Book } from 'src/interfaces/book.interface';

@Injectable()
export class BookService {
  constructor(@InjectModel('Book') private bookModel: Model<Book>) { }

  async findAll(): Promise<Book[]> {
    return this.bookModel.find().exec();
  }
}

book.controller.ts
import { Controller, Get } from '@nestjs/common';
import { BookService } from './book.service';
import { Book } from 'src/interfaces/book.interface';

@Controller('api/book-list')
export class BookController {

  constructor(private readonly bookService: BookService) { }

  @Get()
  findAllBook(): Promise<Book[]> {
    return this.bookService.findAll();
  }

}

ディレクトリ構成

最終的に以下のような構成となりました。(必ずしも同じ構成とする必要はありません。)

src/
|- book
|   |- book.controller.spec.ts
|   |- book.controller.ts
|   |- book.module.ts
|   |- book.service.ts
|- interfaces
|   |- book.interface.ts
|-schemas
|   |- book.schema.ts
|- app.controller.spec.ts
|- app.controller.ts
|- app.module.ts
|- app.service.ts
|- main.ts

APIの動作確認をしてみる

動作確認をするために、以下の環境を作成します。

  • Nest.js で作成した API サーバを開発モードで起動する
  • MongoDB と GUI ツールである Mongo Express を起動する

Nest.js のプロジェクトルートにDockerfileを作成します。

Dockerfile

FROM node:12.14.1

WORKDIR /nest-sample

EXPOSE 3000

※バージョンはきちんと指定したほうがよいです。安易にlatestとか使うと痛い目をみる。。

.dockerignore

node_modules
e2e
docker-compose.ymlを書く
sample/
|- angular-sample
|- nest-sample
|- docker-compose.yml <- ここに配置します。
docker-compose.yml
version: '3.3'
volumes:
  database-data:
  back-libs:

services:
  # ----------------------------------------
  # Backend App
  # ----------------------------------------
  api-server:
    build: ./nest-sample
    container_name: api-server
    working_dir: /nest-sample
    ports:
      - '3000:3000'
    volumes:
      - ./nest-sample:/nest-sample
      - back-libs:/nest-sample/node_modules/
    tty: true
    environment:
      TZ: 'Asia/Tokyo'
    command: bash -c 'npm install && npm run start:dev'
    depends_on:
      - mongo
  # ----------------------------------------
  # Database: MongoDB
  # ----------------------------------------
  mongo:
    image: mongo
    container_name: mongo
    restart: always
    ports:
      - '27017:27017'
    volumes:
      - database-data:/data/db
  # ----------------------------------------
  # GUI Tool: Mongo Express
  # ----------------------------------------
  mongo-express:
    image: mongo-express
    container_name: database-gui-tool
    restart: always
    ports:
      - '8081:8081'

アプリケーション(APIとデータベース)を起動する
$ docker-compose up -d

APIをコールする前に、適当なデータを投入しないと何も返却されないのでサンプルデータを格納します。

http://localhost:8081から、データベースとコレクションを作成します。

データベース作成

image-20200331202216701.png

コレクション作成

image-20200331202249806.png

サンプルデータ投入

好きなデータを入れてください。

データ例;

{
    "_id": ObjectID(),
    "name": "サンプル1",
    "author": "サンプル1",
    "remarks": "サンプル1"
}

image-20200331202526084.png

APIをコールしてみる

リクエスト

GET http://localhost:3000/api/book-list HTTP/1.1

レスポンス

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 346
ETag: W/"15a-/jIupJTsvmnPcivYgNhqhupChmU"
Date: Mon, 30 Mar 2020 22:10:28 GMT
Connection: close

[
  {
    "_id": "5e826dd9545e110009be1beb",
    "name": "サンプル1",
    "author": "サンプル1",
    "remarks": "サンプル1"
  },
  {
    "_id": "5e826de5545e110009be1bed",
    "name": "サンプル2",
    "author": "サンプル2",
    "remarks": "サンプル2"
  },
  {
    "_id": "5e826df5545e110009be1bef",
    "name": "サンプル3",
    "author": "サンプル3",
    "remarks": "サンプル3"
  }
]

OK!

フロントエンド実装

作成したAPIを実際に使用するクライアントを実装します。

HttpClientを使用するための準備

HttpModuleをルートモジュール(app.module.ts)でインポートします。

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    HttpClientModule,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

HttpClientを使用するコンポーネントを作成する
$ ng generate module book-list
$ ng generate component book-list

※moduleを追加したら、ルートモジュールにも作成したモジュールを追加してください。

ルーティングの設定を行う
app-routing.module.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { BookListComponent } from './book-list/book-list.component';


const routes: Routes = [
  { path: '', redirectTo: '/book-list', pathMatch: 'full' },
  { path: 'book-list', component: BookListComponent }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

app.component.html
<router-outlet></router-outlet>

book.ts
export interface Book {
  name: string;
  author: string;
  remarks: string;
}

book-list.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpResponse } from '@angular/common/http';
import { Book } from '../book-list/book';

@Injectable({
  providedIn: 'root'
})
export class BookListService {

  constructor(private readonly httpClient: HttpClient) { }

  async findAll(): Promise<HttpResponse<Book[]>> {
    return await this.httpClient.get<Book[]>('/api/book-list', {
      observe: 'response'
    }).toPromise();
  }
}

book-list.component.ts
import { Component, OnInit } from '@angular/core';
import { BookListService } from '../services/book-list.service';
import { Book } from './book';

@Component({
  selector: 'app-book-list',
  templateUrl: './book-list.component.html',
  styleUrls: ['./book-list.component.css']
})
export class BookListComponent implements OnInit {

  bookList: Book[] = [];

  constructor(private readonly bookListService: BookListService) { }

  ngOnInit(): void {
  }

  async search(): Promise<void> {
    await this.bookListService.findAll().then(response => {
      response.body.forEach(book => {
        this.bookList.push(book);
      });
    });
  }

}

book-list.component.html
<h1>Book List</h1>
<hr />
<div>
  <button (click)="search()">search</button>
</div>
<table>
  <thead>
    <th>本の名前</th>
    <th>著者</th>
    <th>備考</th>
  </thead>
  <tbody>
    <tr *ngFor="let book of bookList">
      <td>{{book.name}}</td>
      <td>{{book.author}}</td>
      <td>{{book.remarks}}</td>
    </tr>
  </tbody>
</table>

proxy.config.json
{
  "/api": {
    "target": "http://api-server:3000"
  }
}

/api/**にリクエストが来た場合に、バックエンドのコンテナにリダイレクトするような設定を追加します。

package.json
{
  "name": "angular-sample",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve --proxy-config proxy.config.json --host 0.0.0.0", // 追加
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  ... 省略
}
動作確認

バックエンド(Nest.js + MongoDB)を起動した状態でフロントエンドのアプリケーションを起動します。

$ npm run start

image-20200402230313549.png

「search」ボタンを押下して、先ほどMongoDBに投入したデータが表示されればOKです。

image-20200402230248989.png

docker-compose.ymlにフロントエンドの設定を追加する

Dockerfile

FROM node:12.14.1

RUN npm install -g @angular/cli

WORKDIR /angular-sample

COPY package.json ./

RUN npm install

COPY . .

EXPOSE 4200

.dockerignore

node_modules
e2e
docker-compose.yml
version: '3.3'
volumes:
  database-data:
  back-libs:
  front-libs:

services:
  # ----------------------------------------
  # Backend App
  # ----------------------------------------
  api-server:
    build: ./nest-sample
    container_name: api-server
    working_dir: /nest-sample
    ports:
      - '3000:3000'
    volumes:
      - ./nest-sample:/nest-sample
      - back-libs:/nest-sample/node_modules/
    tty: true
    environment:
      TZ: 'Asia/Tokyo'
    command: bash -c 'npm install && npm run start:dev'
    depends_on:
      - mongo
  # ----------------------------------------
  # Database: MongoDB
  # ----------------------------------------
  mongo:
    image: mongo
    container_name: mongo
    restart: always
    ports:
      - '27017:27017'
    volumes:
      - database-data:/data/db
  # ----------------------------------------
  # GUI Tool: Mongo Express
  # ----------------------------------------
  mongo-express:
    image: mongo-express
    container_name: database-gui-tool
    restart: always
    ports:
      - '8081:8081'
  # ----------------------------------------
  # Frontend App
  # ----------------------------------------
  front-app:
    build: ./angular-sample
    container_name: front-app
    working_dir: /angular-sample
    ports:
      - '4200:4200'
    volumes:
      - ./angular-sample:/angular-sample
      - front-libs:/angular-sample/node_modules/
    tty: true
    environment:
      TZ: 'Asia/Tokyo'
    command: bash -c 'npm run start'
    depends_on:
      - api-server

http://localhost:4200 にアクセスすれば先ほど作成したバックエンドと連携した画面を使用することができます。

終わりに

作成したサンプルは、こちらに格納してあります。
お手軽に開発できて個人的にはすごく気に入っている技術スタックです!(日本語ドキュメントが少ないことだけが残念)

参考

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