20200223のdockerに関する記事は19件です。

Ubuntu 18.04 LTS(arm64)に Docker & docker-compose をインストールする方法

1. はじめに

今回は、自分自身への備忘録を兼ねて、Raspberry Pi にインストールしたUbuntu 18.04 LTS(arm64)上に Docker & docker-compose をインストールする方法を、お伝えしたいと思います。

2. この記事を読んでできること

  • Ubuntu 18.04 LTS(arm64)で Docker & docker-compose を使えるようになる。

3. 注意事項

  • 特になし。

4. 必要なもの

  • Ubuntu 18.04 LTS(arm64)が動作しているホストOS環境(この記事では Raspberry Pi3B + Ubuntu 18.04 LTS(arm64)を想定)
  • SDカード(16GB以上推奨)
  • インターネット環境(有線LAN推奨)
  • キーボード
  • HDMIディスプレイ

5. Ubuntu 18.04 LTS(arm64)の入手

ダウンロードサイトURL
http://cdimage.ubuntu.com/ubuntu/releases/18.04.4/release/

参考:この記事作成時のファイルバージョン:ubuntu-18.04.4-preinstalled-server-arm64+raspi3.img.xz

参考:ARM-RaspberryPi

6. microSDカードのフォーマット

  • SD Formatter for Windowsなどのソフトを用いて、使用するmicroSDカードをフォーマットする。
  • フォーマット形式は「FAT32」を選択することを推奨します。

  • 参考:参考画面
    fomatter.PNG

7. microSDカードへOSイメージの書き込み(所要時間:5分)

  • Etcherなどのソフトを用いて、使用するmicroSDカードへOSイメージを書き込む。
  • Etcherは、圧縮されたままのOSイメージ(拡張子が.img等)を解凍せずにそのまま扱えておすすめです。

  • 参考:Etcher起動時の画面
    etcher_1.PNG

8. microSDカード の完成

  • 完成した microSDカード を、PCからおもむろに抜き取る。

9. Ubuntu 18.04 LTS(arm64)の起動

  • Raspberry PiにmicroSDカードを挿入し、Ubuntu 18.04 LTS(arm64)を起動する。
デフォルトユーザ名/パスワード
ユーザ名 :ubuntu
パスワード:ubuntu

10. dockerおよびdocker-composeのインストール

コマンドプロンプト
// 新しいパスワードの設定

// IPアドレスの確認
$ ip a

// Windows10などからSSH接続

// パッケージのアップデート
$ sudo apt update
$ sudo apt -y upgrade

// 空き容量の確認
$ df -h

// ラズパイ用PPAの追加
$ sudo add-apt-repository ppa:ubuntu-raspi2/ppa
ENTER

$ sudo apt-get update

$ sudo groupadd -f --system gpio
$ sudo groupadd -f --system i2c
$ sudo groupadd -f --system input
$ sudo groupadd -f --system spi

// パッケージのアップデート
$ sudo apt update

// 必要パッケージをインストール
$ sudo apt install -y apt-transport-https ca-certificates curl software-properties-common

// Dockerの公式GPG keyを追加
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

// repository( stable ) を追加
$ sudo add-apt-repository "deb [arch=arm64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"

// パッケージのアップデート
$ sudo apt update

// docker をインストール
$ sudo apt install -y docker-ce

// Dockerステータスの確認
$ sudo systemctl status docker
q

// Dockerプロセスの確認
$ sudo docker ps

// dockerグループの確認
$ cat /etc/group | grep docker
docker:x:996:

// dockerグループの追加
$ sudo gpasswd -a ubuntu docker

// dockerグループの確認
$ cat /etc/group | grep docker
docker:x:996:ubuntu

// ソケットファイルのパーミッションの確認
$ ls -la /var/run/docker.sock

// ソケットファイルのパーミッションを変更
$ sudo chmod 666 /var/run/docker.sock

// Dockerプロセスの確認
$ docker ps

// python3のインストール
$ sudo apt -y install python3-pip

// 必要ライブラリのインストール
$ sudo apt install libffi-dev
$ sudo apt install libssl-dev

// docker-composeの入手
$ sudo pip3 install -U docker-compose

// docker-composeの確認
$  which docker-compose
/usr/local/bin/docker-compose

// dockerのバージョン確認
# docker -v
Docker version 19.03.6, build 369ce74

// docker-composeのバージョン確認
$  docker-compose -v
docker-compose version 1.25.4, build unknown

11. dockerの動作確認

コマンドプロンプト
// dockerの実行(ハローワールド)
# docker run --rm hello-world

12. docker-composeの動作確認

  • docker-compose.ymlファイルを作成し、ファイル保存する。
docker-compose.yml
version: "2"

services:
  app:
    image: hello-world

  • docker-composeを実行する。
コマンドライン
// docker-compose.ymlファイルが存在することを事前確認
# ls
docker-compose.yml

// docker-composeを実行
# sudo docker-compose up

13. 参考:Dockerコマンド、docker-composeコマンド

14. おわりに

いかがでしたでしょうか?意外と簡単にDocker & docker-composeをはじめることができたのではないかなと思います。
今回の記事が、みなさまの学習の参考になれば幸いです。

2020/02/23 TAKAHIRO NISHIZONO

15. 追記その1:コマンドでのWiFiの設定

コマンドプロンプト
// NetworkManagerをインストール
$ sudo apt -y install network-manager

// OS再起動
$ sudo shutdown -r now

// IPアドレスの確認
$ ip a

// サービスの確認
$ sudo service NetworkManager status
q

// wifiリストの確認(SSID名を確認する)
$ nmcli device wifi list

// wifiの接続
$ sudo nmcli device wifi connect 'SSID名' password 'WIFIパスワード' ifname wlan0

// IPアドレスの確認
$ ip a

// 補足:テキストユーザインターフェースでのWifi接続設定の場合のコマンド
$ nmtui

16. 追記その2:コマンドでのホスト名の設定変更

コマンドプロンプト
// preserve_hostnameをtrueに変更
$ sudo vi /etc/cloud/cloud.cfg

===
#preserve_hostname: false
preserve_hostname: true
===

// ホスト名を設定(デフォルトのubuntu から raspberrypi3 に変更)
$ sudo hostnamectl set-hostname raspberrypi3

// ubuntuのホスト名解決のためだけにsambaを入れる(sambaは使わない)
// これで、WindowsPC側からホスト名で接続できるようになるはず(まれに接続できないこともある)
$ sudo apt-get -y install samba

// OS再起動
$ sudo reboot
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactのアプリを簡単にDocker上のnginxに搭載してみた

目標

Reactのcreate-react-appで作成したアプリをnginxに載せてみました。
簡単にできたのでメモしておきます。

Reactアプリの作成

これはcreate-react-appでサクッと作成しましょう。
DockerでReactの環境を作成してみたでDocker上で構築できるはずです。
アプリが作成できたらyarn buildでnginxに載せるファイルを出力しましょう。

niginxのDockerfile

nginxのDockerfileです。
yarn buildで出力したファイルとnginxの設定ファイルをコピーしています。

FROM nginx:1.17

COPY ./app/build /opt/app/

COPY ./nginx.conf /etc/nginx/nginx.conf

CMD ["nginx", "-g", "daemon off;", "-c", "/etc/nginx/nginx.conf"]

nginxの設定ファイル

locationはbuildしたファイルを置く場所に設定しましょう。

nginx.conf
user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;

    keepalive_timeout  75;

    server {
        listen 80;
        charset utf-8;

        location /{
            root   /opt/app/;
            index  index.html;
        }
    }
}

まとめ

あとは、nginxのDockerfileをbuildしてコンテナを立ち上げれば完了です。

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

sbt-native-packagerでdockerイメージのベースをamazoncorrettoにしたい

build.sbtに以下を追加します。

dockerBaseImage := "amazoncorretto:{VERSION}"

image.png

ところが

sbt "docker:publishLocal"

でビルドしようとすると

[error] java.lang.RuntimeException: Nonzero exit value: 127
[error]     at com.typesafe.sbt.packager.docker.DockerPlugin$.publishLocalDocker(DockerPlugin.scala:564)
[error]     at com.typesafe.sbt.packager.docker.DockerPlugin$.$anonfun$projectSettings$37(DockerPlugin.scala:209)
[error]     at com.typesafe.sbt.packager.docker.DockerPlugin$.$anonfun$projectSettings$37$adapted(DockerPlugin.scala:201)
[error]     at scala.Function1.$anonfun$compose$1(Function1.scala:44)
[error]     at sbt.internal.util.$tilde$greater.$anonfun$$u2219$1(TypeFunctions.scala:40)
[error]     at sbt.std.Transform$$anon$4.work(System.scala:67)
[error]     at sbt.Execute.$anonfun$submit$2(Execute.scala:269)
[error]     at sbt.internal.util.ErrorHandling$.wideConvert(ErrorHandling.scala:16)
[error]     at sbt.Execute.work(Execute.scala:278)
[error]     at sbt.Execute.$anonfun$submit$1(Execute.scala:269)
[error]     at sbt.ConcurrentRestrictions$$anon$4.$anonfun$submitValid$1(ConcurrentRestrictions.scala:178)
[error]     at sbt.CompletionService$$anon$2.call(CompletionService.scala:37)
[error]     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error]     at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
[error]     at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[error]     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
[error]     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
[error]     at java.lang.Thread.run(Thread.java:748)
[error] (Docker / publishLocal) Nonzero exit value: 127
[error] Total time: 77 s, completed 2020/02/22 11:21:07

といったエラーが出て失敗してしまいます :innocent:
生成されたDockerfileを覗いてみると

RUN id -u demiourgos728 1>/dev/null 2>&1 || (( getent group 0 1>/dev/null 2>&1 || ( type groupadd 1>/dev/null 2>&1 && groupadd -g 0 root || addgroup -g 0 -S root )) && ( type useradd 1>/dev/null 2>&1 && useradd --system --create-home --uid 1001 --gid 0 demiourgos728 || adduser -S -u 1001 -G root demiourgos728 ))

といった行があります。
amazoncorrettoはamazonlinuxをベースにしていますが、amazonlinuxではaddgroupadduserが使えないため(参考1参考2)エラーが発生していると考えられます。

参考先にもある通り、

RUN yum install -y shadow-utils

を挟んであげる事で回避できますが、そもそも動かすだけならシンプルな設定でも十分なのでDockerfileをスクラッチで書き直してしまいましょう。
https://www.scala-sbt.org/sbt-native-packager/formats/docker.html#write-from-scratch

import com.typesafe.sbt.packager.docker._

dockerCommands := Seq(
  Cmd("FROM", "amazoncorretto:{VERSION}"),
  Cmd("COPY", "opt", "/opt"),
  ExecCmd("ENTRYPOINT", "/opt/docker/bin/{APP_NAME}")
)

生成されるDockerfileを確認するためには以下のコマンドが使えます。

sbt "show dockerCommands"
[info] * Cmd(FROM,WrappedArray(amazoncorretto:{VERSION}))
[info] * Cmd(COPY,WrappedArray(opt, /opt))
[info] * ExecCmd(ENTRYPOINT,WrappedArray(/opt/docker/bin/{APP_NAME}))

最低限の内容になっている事が確認できます。
ビルドも無事成功します。

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

docker commitからimageをレジストリに登録メモ

sudo docker ps 

sudo docker commit [containerID] [repository:tag]

sudo docker images

# ローカルにファイルとして保存する。 (大抵の場合必要なし。)
sudo docker save [imageID] -o [filename]

#tag付け
sudo docker tag [imageID] [registry IP:port]/[dirName]:[version]

#registry に push
sudo docker push [registry IP:port]/[dirName]:[version]

sudo docker ps 

sudo docker commit aaebff40fcbf python3_gpu:0.1

sudo docker images

# 初めのバージョンなので、0.1かつ latest
sudo docker tag zzjhskjahck 30.0.0.0:5000/python3_gpu_base:0.1
sudo docker tag zzjhskjahck 30.0.0.0:5000/python3_gpu_base:latest

sudo docker push 30.0.0.0:5000/python3_gpu_base:0.1
sudo docker push 30.0.0.0:5000/python3_gpu_base:latest
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker commit から image を private registry に登録メモ

sudo docker ps 

sudo docker commit [containerID] [repository:tag]

sudo docker images

# ローカルにファイルとして保存する。 (大抵の場合必要なし。)
sudo docker save [imageID] -o [filename]

#tag付け
sudo docker tag [imageID] [registry IP:port]/[dirName]:[version]

#registry に push
sudo docker push [registry IP:port]/[dirName]:[version]

sudo docker ps 

sudo docker commit aaebff40fcbf python3_gpu:0.1

sudo docker images

# 初めのバージョンなので、0.1かつ latest
sudo docker tag zzjhskjahck 30.0.0.0:5000/python3_gpu_base:0.1
sudo docker tag zzjhskjahck 30.0.0.0:5000/python3_gpu_base:latest

sudo docker push 30.0.0.0:5000/python3_gpu_base:0.1
sudo docker push 30.0.0.0:5000/python3_gpu_base:latest

取得の場合

docker pull [IPaddress]:[port]/[imageName]:[tagName]

#例
docker pull 30.0.0.0:5000/python3_gpu_base:latest
  • Docker Private Registry (port 10080番)にいくとUIで見れて、そこにコマンドも載っている
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

手探りDocker #3

前回のエラー解消の続き

前回の記事
手探りDocker #2

最初の記事
手探りDocker #1

いくつか警告やエラーが出ていたが、警告は無視することに

以下のエラーだけの解消を引き続き目指します。
image.png

サンプルで使用している下記参考サイトの「Start a Redis container」をすっ飛ばしていたので
docker docs
docker run -dt redisをやってみる → うまくいかない

エラー文の「Drive sharing failed for an unknown reason」が手掛かりになりそうなので、この文字列でググってみたところ、以下を見つけた
WindowsのDockerで「Drive sharing failed for an unknown reason」エラーが出た

設定に問題があるとのこと
ウインドウ右下のDockerのアイコンをクリック → Settings
image.png

以下の設定画面が表示される。
リンク記事とdockerのバージョンが違うので、手順が異なるが、Resources → FILE SHARING とすすめる
image.png

共有したいドライブを選択する
私の環境はCのみ(そもそもなぜ共有するの・・・汗 理解してなさす)

右下のApply & Restartを選択
image.png
すると以下のエラー
image.png
原因はわからなかったが、とりあえず現在起動しているコンテナを停止した
image.png
改めて実行すると、今度はうまくいった
image.png

もう一度docker-compose up --buildを実行します
image.png
どうやらうまくいったらしい・・・。
辛い・・・。
何一つ理解していない気がするが・・・。
image.png

example-voting-app_vote_1が投票のWebアプリのようなので、ブラウザでアクセスしてみる
image.png
※「http://localhost:5000/」のURLにアクセスします
image.png

うーん・・・CATSとDOGSをクリックするたびにPostgresにデータがインサートされているようなされていないような・・・
docker docsには
image.png
とあるので、複数のコンテナ内のアプリが通信し合ってこのアプリケーションは成り立っているのだと思います。
分散アプリケーション?

理解が全くできていない

とりあえず、Docker公式の入門をやってみるかと挑んだものの、仮想化の基礎知識、ネットワークの知識が不足していて、ほとんどDockerというものを理解できなかったです。また、チャレンジしてみて「こういったことができるようになる」「こういったことが理解できていること」等の目的がなかったことがフワフワした結果を招いたとも言えそうです。

次回以降のDocker系の記事では上記基礎知識をすこしずつ勉強していこうと思います。
そして、今回取得したサンプルアプリに対して以下を行うことを目標としたいと思います。

  • 各コンテナにTera Term WinSCPで接続、操作する
  • 投票アプリのデータを、SQLクライアントアプリでPostgresに接続して、データが溜まっているか確認してみる

今回は以上です。

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

Build Tensorflow v2.1.0 v1-API version full installer with TensorRT 7 enabled [Docker version]

This is the procedure to build all by yourself without using NGC containers.

1. Environment

  • Ubuntu 18.04 x86_64 RAM:16GB
  • Geforce GTX 1070
  • NVIDIA Driver 440.59
  • CUDA 10 (V10.0.130)
  • cuDNN 7.6.5.32
  • Docker 19.03.6, build 369ce74a3c
  • Tensorflow v2.1.0
  • TensorRT 7
  • TF-TRT
  • Bazel 0.29.1
  • Python 3.6

2. Procedure

Create_working_directory
$ cd ~
$ mkdir work/tensorrt && cd work/tensorrt

Download TensorRT-7.0.0.11 and copy to the work/tensorrt directory.
https://developer.nvidia.com/compute/machine-learning/tensorrt/secure/7.0/7.0.0.11/tars/TensorRT-7.0.0.11.Ubuntu-18.04.x86_64-gnu.cuda-10.0.cudnn7.6.tar.gz

Create_Dockerfile
$ nano Dockerfile
Dockerfile
FROM nvidia/cuda:10.0-cudnn7-devel-ubuntu18.04

RUN apt-get update && \
    DEBIAN_FRONTEND=noninteractive apt-get install -y \
    protobuf-compiler python-pil python-lxml python-tk cython \
    autoconf automake libtool curl make g++ unzip wget git nano \
    libgflags-dev libgoogle-glog-dev liblmdb-dev libleveldb-dev \
    libhdf5-serial-dev libhdf5-dev python3-opencv python-opencv \
    python3-dev python3-numpy python3-skimage gfortran libturbojpeg \
    python-dev python-numpy python-skimage python3-pip python-pip \
    libboost-all-dev libopenblas-dev libsnappy-dev software-properties-common \
    protobuf-compiler python-pil python-lxml python-tk libfreetype6-dev pkg-config \
    libpng-dev libhdf5-100 libhdf5-cpp-100 libc-ares-dev libblas-dev \
    libeigen3-dev libatlas-base-dev openjdk-8-jdk libopenblas-base \
    openmpi-bin libopenmpi-dev gcc libgfortran5 libatlas3-base liblapack-dev

RUN pip3 install pip --upgrade && \
    pip3 install Cython && \
    pip3 install contextlib2 && \
    pip3 install pillow && \
    pip3 install lxml && \
    pip3 install jupyter && \
    pip3 install matplotlib && \
    pip3 install keras_applications==1.0.8 --no-deps && \
    pip3 install keras_preprocessing==1.1.0 --no-deps && \
    pip3 install h5py==2.9.0 && \
    pip3 install -U --user six numpy wheel mock && \
    pip3 install pybind11 && \
    pip2 install Cython && \
    pip2 install contextlib2 && \
    pip2 install pillow && \
    pip2 install lxml && \
    pip2 install jupyter && \
    pip2 install matplotlib

# Create working directory
RUN mkdir -p /tensorrt && \
    cd /tensorrt
ARG work_dir=/tensorrt
WORKDIR ${work_dir}

# Clone Tensorflow v2.1.0, TF-TRT and install Bazel 
RUN git clone -b v2.1.0 --depth 1 https://github.com/tensorflow/tensorflow.git && \
    git clone --recursive https://github.com/NobuoTsukamoto/tf_trt_models.git && \
    wget https://github.com/bazelbuild/bazel/releases/download/0.29.1/bazel-0.29.1-installer-linux-x86_64.sh && \
    chmod +x bazel-0.29.1-installer-linux-x86_64.sh && \
    bash ./bazel-0.29.1-installer-linux-x86_64.sh

# Install TensorRT-7
COPY TensorRT-7.0.0.11.Ubuntu-18.04.x86_64-gnu.cuda-10.0.cudnn7.6.tar.gz ${work_dir}
RUN tar -xvzf TensorRT-7.0.0.11.Ubuntu-18.04.x86_64-gnu.cuda-10.0.cudnn7.6.tar.gz && \
    rm TensorRT-7.0.0.11.Ubuntu-18.04.x86_64-gnu.cuda-10.0.cudnn7.6.tar.gz

# Setting environment variables
ENV TRT_RELEASE=${work_dir}/TensorRT-7.0.0.11
ENV PATH=/usr/local/cuda-10.0/bin:$TRT_RELEASE:$TRT_RELEASE/bin:$PATH \
    LD_LIBRARY_PATH=/usr/local/cuda-10.0/lib64:$TRT_RELEASE/lib:$LD_LIBRARY_PATH \
    TF_CUDA_VERSION=10.0 \
    TF_CUDNN_VERSION=7 \
    TENSORRT_INSTALL_PATH=$TRT_RELEASE \
    TF_TENSORRT_VERSION=7
Create_DockerImage
$ docker build --tag tensorrt .
Check_DockerImage_generation_status
$ docker images

REPOSITORY  TAG     IMAGE ID      CREATED         SIZE
tensorrt    latest  cb6f0fc656d1  17 seconds ago  9.04GB
Start_Docker_container
$ docker run \
  --gpus all \
  --name tensorrt \
  -it \
  --privileged \
  -p 8888:8888 \
  tensorrt \
  /bin/bash
CUDA_and_cuDNN_version_check
# nvcc -V

nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2018 NVIDIA Corporation
Built on Sat_Aug_25_21:08:01_CDT_2018
Cuda compilation tools, release 10.0, V10.0.130

# cat /usr/include/cudnn.h | grep '#define'

#define CUDNN_MAJOR 7
#define CUDNN_MINOR 6
#define CUDNN_PATCHLEVEL 5

# nvidia-smi

+-----------------------------------------------------------------------------+
| NVIDIA-SMI 440.59       Driver Version: 440.59       CUDA Version: 10.2     |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  GeForce GTX 107...  Off  | 00000000:01:00.0 Off |                  N/A |
| N/A   60C    P5    11W /  N/A |    410MiB /  8119MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
+-----------------------------------------------------------------------------+

# bazel version

Extracting Bazel installation...
WARNING: --batch mode is deprecated. Please instead explicitly shut down your Bazel server using the command "bazel shutdown".
Build label: 0.29.1
Build target: bazel-out/k8-opt/bin/src/main/java/com/google/devtools/build/lib/bazel/BazelServer_deploy.jar
Build time: Tue Sep 10 13:44:39 2019 (1568123079)
Build timestamp: 1568123079
Build timestamp as int: 1568123079
Check_folder_hierarchy
# echo $PWD
/tensorrt

# ls -l
total 12
drwxr-xr-x 10 root root 4096 Dec 17 02:30 TensorRT-7.0.0.11
drwxr-xr-x  7 root root 4096 Feb 22 16:16 tensorflow
drwxr-xr-x  8 root root 4096 Feb 22 16:16 tf_trt_models
Initial_configuration_of_Tensorflow_v2.1.0
# cd tensorflow
# ./configure

Extracting Bazel installation...
WARNING: --batch mode is deprecated. Please instead explicitly shut down your Bazel server using the command "bazel shutdown".
You have bazel 0.29.1 installed.
Please specify the location of python. [Default is /usr/bin/python]: /usr/bin/python3

Found possible Python library paths:
  /usr/lib/python3/dist-packages
  /usr/local/lib/python3.6/dist-packages
Please input the desired Python library path to use.  Default is [/usr/lib/python3/dist-packages]
/usr/local/lib/python3.6/dist-packages

Do you wish to build TensorFlow with XLA JIT support? [Y/n]: n
No XLA JIT support will be enabled for TensorFlow.

Do you wish to build TensorFlow with OpenCL SYCL support? [y/N]: n
No OpenCL SYCL support will be enabled for TensorFlow.

Do you wish to build TensorFlow with ROCm support? [y/N]: n
No ROCm support will be enabled for TensorFlow.

Do you wish to build TensorFlow with CUDA support? [y/N]: y
CUDA support will be enabled for TensorFlow.

Do you wish to build TensorFlow with TensorRT support? [y/N]: y
TensorRT support will be enabled for TensorFlow.

Found CUDA 10.0 in:
    /usr/local/cuda-10.0/lib64
    /usr/local/cuda-10.0/include
Found cuDNN 7 in:
    /usr/lib/x86_64-linux-gnu
    /usr/include
Found TensorRT 7 in:
    /tensorrt/TensorRT-7.0.0.11/lib
    /tensorrt/TensorRT-7.0.0.11/include

Please specify a list of comma-separated CUDA compute capabilities you want to build with.
You can find the compute capability of your device at: https://developer.nvidia.com/cuda-gpus.
Please note that each additional compute capability significantly increases your build time and binary size, and that TensorFlow only supports compute capabilities >= 3.5 [Default is: 3.5,7.0]: 6.1

Do you want to use clang as CUDA compiler? [y/N]: n
nvcc will be used as CUDA compiler.

Please specify which gcc should be used by nvcc as the host compiler. [Default is /usr/bin/gcc]: 

Please specify optimization flags to use during compilation when bazel option "--config=opt" is specified [Default is -march=native -Wno-sign-compare]: 

Would you like to interactively configure ./WORKSPACE for Android builds? [y/N]: n
Not configuring the WORKSPACE for Android builds.

Preconfigured Bazel build configs. You can use any of the below by adding "--config=<>" to your build command. See .bazelrc for more details.
    --config=mkl            # Build with MKL support.
    --config=monolithic     # Config for mostly static monolithic build.
    --config=ngraph         # Build with Intel nGraph support.
    --config=numa           # Build with NUMA support.
    --config=dynamic_kernels    # (Experimental) Build kernels into separate shared objects.
    --config=v2             # Build TensorFlow 2.x instead of 1.x.
Preconfigured Bazel build configs to DISABLE default on features:
    --config=noaws          # Disable AWS S3 filesystem support.
    --config=nogcp          # Disable GCP support.
    --config=nohdfs         # Disable HDFS support.
    --config=nonccl         # Disable NVIDIA NCCL support.
Configuration finished

Build with 16GB of RAM and 8 cores. You need to adjust according to the resources of your PC environment. Calculate assuming that about 2GB of RAM is consumed for each core.

Build_Tensorflow_v2.1.0
# bazel build \
  --config=opt \
  --config=cuda \
  --config=noaws \
  --config=nohdfs \
  --config=nonccl \
  --config=v1 \
  --local_resources=16384.0,8.0,1.0 \
  --host_force_python=PY3 \
  --noincompatible_do_not_split_linking_cmdline \
  //tensorflow/tools/pip_package:build_pip_package
Build_wheel
# ./bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg

Sun Feb 23 11:13:32 UTC 2020 : === Preparing sources in dir: /tmp/tmp.2tVlFrnLCQ
/tensorrt/tensorflow /tensorrt/tensorflow
/tensorrt/tensorflow
/tmp/tmp.2tVlFrnLCQ/tensorflow/include /tensorrt/tensorflow
/tensorrt/tensorflow
Sun Feb 23 11:13:45 UTC 2020 : === Building wheel
warning: no files found matching 'README'
warning: no files found matching '*.pyd' under directory '*'
warning: no files found matching '*.pd' under directory '*'
warning: no files found matching '*.dylib' under directory '*'
warning: no files found matching '*.dll' under directory '*'
warning: no files found matching '*.lib' under directory '*'
warning: no files found matching '*.csv' under directory '*'
warning: no files found matching '*.h' under directory 'tensorflow_core/include/tensorflow'
warning: no files found matching '*' under directory 'tensorflow_core/include/third_party'
Sun Feb 23 11:14:06 UTC 2020 : === Output wheel file is in: /tmp/tensorflow_pkg

# cp /tmp/tensorflow_pkg/tensorflow-2.1.0-cp36-cp36m-linux_x86_64.whl /tensorrt
# cd ..
# ls -l

total 179920
drwxr-xr-x 10 root root      4096 Dec 17 02:30 TensorRT-7.0.0.11
-rwxr-xr-x  1 root root  43791980 Sep 10 13:57 bazel-0.29.1-installer-linux-x86_64.sh
drwxr-xr-x  1 root root      4096 Feb 23 06:43 tensorflow
-rw-r--r--  1 root root 140419710 Feb 23 11:15 tensorflow-2.1.0-cp36-cp36m-linux_x86_64.whl
drwxr-xr-x  8 root root      4096 Feb 23 06:09 tf_trt_models
Install_tensorflow_v2.1.0_v1-API_with_TensorRT_CUDA10.0_cuDNN7.6.5
# pip3 uninstall tensorflow-gpu tensorflow
# pip3 install tensorflow-2.1.0-cp36-cp36m-linux_x86_64.whl
Install_TF-TRT
# cd tf_trt_models
# ./install.sh python3
Import_test
# python3
Python 3.6.9 (default, Nov  7 2019, 10:44:02) 
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.

>>> import tensorflow as tf
2020-02-23 12:01:46.274803: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.0
2020-02-23 12:01:46.768715: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libnvinfer.so.7
2020-02-23 12:01:46.769356: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libnvinfer_plugin.so.7

>>> from tensorflow.python.client import device_lib
>>> device_lib.list_local_devices()
2020-02-23 12:05:28.927561: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcuda.so.1
2020-02-23 12:05:28.987702: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-02-23 12:05:28.988503: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1555] Found device 0 with properties: 
pciBusID: 0000:01:00.0 name: GeForce GTX 1070 with Max-Q Design computeCapability: 6.1
coreClock: 1.2655GHz coreCount: 16 deviceMemorySize: 7.93GiB deviceMemoryBandwidth: 238.66GiB/s
2020-02-23 12:05:28.988540: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.0
2020-02-23 12:05:28.988579: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcublas.so.10.0
2020-02-23 12:05:29.006807: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcufft.so.10.0
2020-02-23 12:05:29.013005: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcurand.so.10.0
2020-02-23 12:05:29.058509: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusolver.so.10.0
2020-02-23 12:05:29.087138: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusparse.so.10.0
2020-02-23 12:05:29.087290: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudnn.so.7
2020-02-23 12:05:29.087578: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-02-23 12:05:29.090019: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-02-23 12:05:29.091523: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1697] Adding visible gpu devices: 0
2020-02-23 12:05:29.091629: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.0
2020-02-23 12:05:30.075656: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1096] Device interconnect StreamExecutor with strength 1 edge matrix:
2020-02-23 12:05:30.075707: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1102]      0 
2020-02-23 12:05:30.075740: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1115] 0:   N 
2020-02-23 12:05:30.075938: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-02-23 12:05:30.076505: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-02-23 12:05:30.077038: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero
2020-02-23 12:05:30.077521: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1241] Created TensorFlow device (/device:GPU:0 with 7225 MB memory) -> physical GPU (device: 0, name: GeForce GTX 1070 with Max-Q Design, pci bus id: 0000:01:00.0, compute capability: 6.1)
[name: "/device:CPU:0"
device_type: "CPU"
memory_limit: 268435456
locality {
}
incarnation: 11937878305894780308
, name: "/device:GPU:0"
device_type: "GPU"
memory_limit: 7576322048
locality {
  bus_id: 1
  links {
  }
}
incarnation: 7297894123203666970
physical_device_desc: "device: 0, name: GeForce GTX 1070 with Max-Q Design, pci bus id: 0000:01:00.0, compute capability: 6.1"
]
>>>

3. Appendix

3-1. Commit container image

Escape from Docker Container.
Ctrl + P
Ctrl + Q

Commit_container_image
$ docker commit tensorrt tensorrt

3-2. Extract_wheel_from_Docker_container

Extract_wheel_from_Docker_container
$ docker cp cb6f0fc656d1:/tensorrt/tensorflow-2.1.0-cp36-cp36m-linux_x86_64.whl .

3-3. Download Pre-build wheel

Download_Pre-build_wheel
$ curl -sc /tmp/cookie "https://drive.google.com/uc?export=download&id=10uFrl9X0evjKI8rH7cMfNIL5K0-iOnNU" > /dev/null
$ CODE="$(awk '/_warning_/ {print $NF}' /tmp/cookie)"
$ curl -Lb /tmp/cookie "https://drive.google.com/uc?export=download&confirm=${CODE}&id=10uFrl9X0evjKI8rH7cMfNIL5K0-iOnNU" -o tensorflow-2.1.0-cp36-cp36m-linux_x86_64.whl

4. Reference articles

4-1. Various

  1. https://github.com/NVIDIA/TensorRT
  2. https://github.com/NobuoTsukamoto/tf_trt_models
  3. https://docs.nvidia.com/deeplearning/frameworks/tf-trt-user-guide/index.html
  4. https://www.google.com/search?q=NvInferVersion.h+-www.sejuku.net&oq=NvInferVersion.h&aqs=chrome..69i57.1147j0j8&sourceid=chrome&ie=UTF-8
  5. https://hub.docker.com/layers/nvidia/cuda/10.0-cudnn7-devel-ubuntu18.04/images/sha256-e277b9eef79d6995b10d07e30228daa9e7d42f49bcfc29d512c1534b42d91841?context=explore
  6. https://qiita.com/ksasaki/items/b20a785e1a0f610efa08
  7. https://github.com/tensorflow/tensorrt
  8. Jetson NanoでTF-TRTを試す(Image Classification) - nb.oの日記
  9. Jetson NanoでTF-TRTを試す(Object detection) - nb.oの日記

4-2. CUDA/cuDNN/TensorRT Header files and libraries search path logic

  1. https://github.com/tensorflow/tensorflow/blob/v2.1.0/configure.py
  2. https://github.com/tensorflow/tensorflow/blob/v2.1.0/third_party/gpus/find_cuda_config.py

4-3. Check GPU Compute Capability

  1. https://developer.nvidia.com/cuda-gpus
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

実務未経験エンジニアはDockerを最低限勉強しておく。という提案

こんにちは。

おかげさまで先日めでたく駆け出しエンジニアとしてのスタートを切りました。
就職するまでの経緯については
【実体験をもとに】30歳未経験から独学4ヶ月でバックエンドエンジニアとしてWeb系自社開発企業へ転職するまでのロードマップ
こちらに記載してます。

さてさてそんな私が出勤初日から「勉強しておいてよかった〜」と思ったスキルがありまして、それがDockerです。

内容をYouTube動画でも投稿してるので是非見ください

https://youtu.be/j0jXUIF9KKk

これから駆け出しプログラマーとしての転職を目指す方々は是非学んでおいたほうが良いと感じたので、私自身の振り返りも含め紹介します。

併せて、「これちゃんと勉強しておけばよかった…」と後悔している技術も紹介します。

なぜDcokerを勉強しておいて良かったか

スタートで躓かなかった

入社初日の挨拶等が終わり、帰宅したあと会社からGitHubの招待がきました。

会社にジョインして最初にやることは(挨拶とか説明を抜きにすると)開発環境の構築というのはきっとどの企業でも同じでしょう。

昨今のモダンなWeb企業ではDockerでの環境構築が主流なので、例に漏れず招待されたGitHubのREAD MEにもDockerでの構築を行う旨が記載されていました。

入社して最初に打ったコマンドがGit clone
そして次がdocker-compose buildです

ここでもし私がDockerについて学んでなければ、開発環境の構築をするための勉強からスタートすることになったでしょう。

「仕事をスタートすることができない」という状況は評価者からのイメージも良くないですし、自分自身のメンタルにもかなりのダメージだと思います(汗)

どんな勉強をしておけばいいのか

Dockerはただ構築に使うだけなら簡単なものです。
コマンドを打つだけですからね。
しかしDockerの概念や構造をそれなりに学んでおかないと後々に困ることがあります。

いざエラーが発生した時にlogの確認ができなかったり。
DBの中身を確認したくてもどのコンテナにどう入れば良いか分からなかったり。

仕事をする上で必ず触れるポイントなので、入社前に学習しましょう。

とりあえず入社前は以下のような内容を学んでおけ良いのではないでしょうか?
- コンテナの概念
- Dockerfileの内容
- docker-compose.ymlの内容
- docker-composeコマンドの使い方
(build.up.down.runをとりあえず)
- dockerコンテナへの入り方(docker exec -i -t コンテナID bash)

ハードルが高く感じがちですが、ネット上に情報がたくさん落ちてますので参考にすれば初学者でもちゃんと扱えるようになります。
というか、Dockerfileやdocker-compose.ymlはテンプレがたくさん落ちてるのでとにかく構築してポートフォリオに導入してみるのが早道でしょう。

コンテナの概念については文章で見るより説明を受けたほうが早い。

ネット上には図を使って説明している記事など多くあり情報ソースに事欠かないDockerですが、なかでもYoutubeで解説動画を出しているベテランエンジニアさんがいます。
私がヘビーローテションしていたオススメのYouTuberさんを紹介します。

くろかわこうへい さんの「目指せコンテナマスター!」シリーズ

今から追いつくDocker講座!AWS ECSとFargateで目指せコンテナマスター!〜シリーズ1回目〜

IMAGE ALT TEXT HERE
※くろかわさんご本人の許可を得て掲載しています。

とても丁寧に説明してくださっているので、わざわざ他で調べる必要がないくらいです(๑❛౪❛๑)
まずは前半のコンテナについての概要をサックリと理解すると良いのではないでしょうか。

他にもAWSについての解説を出していらっしゃるので必見です。

個人的にはくろかわさんの喋り方がクセになるんですよね〜w

SQL

ついでにSQLも勉強しておいてよかったな〜と思ったので紹介します。

入社してたった数日間ですが、一日中DBを触ってた日もありました。
難しいことができる必要は無いですが、基本的な動作は行えるようにしておいて良かったと思います。

基本的な動作って何?って言うと、以下のサイトに書いてある内容くらいは把握していれば良いのではないでしょうか。
https://yukun.info/category/sql/

SQLクエリを打って検索や追加を行えるって感じですね。

※ちなみに私はmigrationエラーするので、入社時もschema_migrationsを見に行ったりダブってるmigrationファイルをinsertしたりしてました。

勉強しておけば良かったと後悔したこと

rails consoleの使い方

未経験エンジニアがポートフォリオを作る過程でデバッグ作業をしっかり行なう。ということは少ないのではないでしょうか?

既存の商材にもバグは必ず発生しますし、その際には他人が作ったコードが実際にどう動作しているのか知らなければ直せません。

私はポートフォリオ作成においてrails consoleをしっかりと使用していなかったので、メソッドの動作確認がスムーズにできませんでした。

リファクタリング

環境構築が終わったら、既存のソースコードの内容を把握する作業が必要だと思います。

恥ずかしながら僕はまともにリファクタリングの勉強をしていなかったので、まさにちんぷんかんぷんでした…

scopeで式をメソッドとして定義したりviewを2ファイルに分割するなど、ちゃんと勉強や活用をしておかないとコードの把握に時間がかかりメンタルが・・・。

mailer

「開発をしていれば100%mailerを使う機会がある。」ってなんで思いつかなかったんだろう…。
と後悔する限りです。

未経験者のポートフォリオにはユーザー登録時にメールが届くくらいの機能は付けておくべきでしょう!

終わり

入社して1週間になりますが、正直「何もできなかった・・・」というのが感想です。
転職前は転職を有利にするスキルを身につけることに頭がいっぱいでしたが、実践に役立つスキルに対しアンテナを張って学ぶことも未経験エンジニアには大切なことだなと思いました。

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

Laravelでミニブログを作ろう #3

前回:https://qiita.com/customaddone/items/7563a3252e4debc05f1a
GitHub:https://github.com/customaddone/larablog

今回はdockerでterraformのコンテナを立てます

Terraform概要

Terraformとは

コードによりインフラの構築、変更を行うツール
AWS,GCPなどのクラウド上のインフラに手を加えることができる

Terraformのメリット

GUIを通さずインフラに変更を加えられる(ブラウザ上からの操作によるミスを防げる)
一度書いたコードを使い回すことができる

Dockerfile

まずdockerディレクトリ直下にTerraform用のディレクトリを追加して下さい

docker/terraform/Dockerfile
FROM alpine:3.10

# 最新バージョン
ARG terraform_version="0.12.21"

# Terraformインストール用のコマンドを使えるようにwget unzip curlをインストール
# Terraformをインストールして解凍する
# ディレクトリを作成する
RUN apk update --no-cache \
    && apk add --no-cache \
        wget \
        unzip \
        curl \
    && wget https://releases.hashicorp.com/terraform/${terraform_version}/terraform_${terraform_version}_linux_amd64.zip \
    && unzip ./terraform_${terraform_version}_linux_amd64.zip -d /usr/local/bin/ \
    && rm -rf ./terraform_${terraform_version}_linux_amd64.zip \
    && mkdir terraform

WORKDIR /terraform

# いちいちterraformコマンドを打つのが面倒な場合
# CMD ["terraform"]

docker-compose

docker-compose.yaml
terraform:
    build:
      context: .
      dockerfile: docker/terraform/Dockerfile
    volumes:
      - ./terraform:/terraform/ 
    environment:
      - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID}
      - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY}
      - AWS_REGION=${AWS_DEFAULT_REGION}
    tty: true

environmentでAWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEYを指定しますが
、直に書いたままgit pushするとGitHub上に反映され、キーの情報が全世界に向けて公開される(最悪赤の他人にキーを悪用されてMAX課金されてしまう)ので、環境変数を用います

.env
AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxx
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxx
AWS_DEFAULT_REGION=xxxxxxxxxxxxxxx

docker-compose.yamlはデフォルトで.envの情報を読み込むので、.envに変数の値(キーの情報)を書くと、docker-compose.yamlでその値を使えます
.envはgitignoreに書いてあるので、git pushしてもGitHub上に反映されません

これでdocker-compose up -d した後、docker-compose exec terraform shしてterraformコンテナの中に入ることでterraformを利用できます

Terraformの使用

試しにVPCを立ててみましょう

larablog
  ├── terraform
           ├── vpc # 追加
terraform/vpc/aws_vpc.tf
# ファイルの末尾にtfをつけてください
# VPCは他のネットワークから論理的に切り離されたネットワークです
# EC2などのリソースを配置します
resource "aws_vpc" "example" {

  #vpcのipv4のアドレス範囲を設定します
  # 10.0.0.0〜10.0.255.255まで使えるよ
  cidr_block           = "10.0.0.0/16"

  # AWSのDNSサーバーによる名前解決を有効にする
  # route53が使えるようになる
  enable_dns_support   = true
  enable_dns_hostnames = true

  # タグをつけてコンソール上でわかりやすく
  tags = {
    Name = "larablog"
  }
}

vpcディレクトリに移動してterraform init, terraform applyするとvpcが作成されます。
が、作成と同時にvpcディレクトリにterraform.tfstate等のファイルができます(どでかい)。これらはgitignoreに書き込んでgit管理しないようしましょう

.gitignore
...

# Local .terraform directories
/terraform/**/.terraform/* # 追加

# .tfstate files
/terraform/**/*.tfstate # 追加
/terraform/**/*.tfstate.* # 追加

...

これでterraform init, terraform applyコマンドを打つとvpcが立ち上がります。AWSのコンソールで確認してみてください

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

階層構造をもつTODOリストの作り方

はじめに

以前、階層構造をもつTODOリストをWebアプリケーションとして実装したので、その知見を共有します。

実際に作成したTODOリスト(OSSとして公開)


機能一覧

  • メモの新規作成
  • メモの編集(編集前の内容がテキストボックスに入力されている)
  • メモの削除
  • メモの追加(メモAの下にメモBを紐付け)
  • メモを一つ上の階層に移動
  • メモの一括削除
  • 新規作成や編集時のテキストボックスを、ボックス外の任意の場所をクリックすることで非表示(UX)
  • MemoモードとTaskモードの切り替え
  • (Taskモードのみ) メモ横のチェックボックスにチェックを入れると、非同期で打ち消し線を表示

解説

技術スタックは、Docker Compose、Java、PostgreSQLがメインです。

内部では基本的にCRUDしか行っておらず、DB側で再帰的なデータを保存しています。

Docker Compose

portsを定義して、ホストとゲストのポートをマッピングしています。

サービス名tomcatpostgresでお互い接続は可能なはずですが、ipv4も固定していました(理由は失念してしまいました)。

version: "3.7"
services:
  tomcat:
# When building from source code, uncomment following build
# and remove local image. e.g.) docker rmi resotto/tomcat:1.0
#    build: ap/
    image: resotto/tomcat:1.0
    container_name: tomcat
    tty: true
    ports:
     - "8888:8080"
    networks:
      app_net:
        ipv4_address: 172.16.1.3

  postgres:
# When building from source code, uncomment following build
# and remove local image. e.g.) docker rmi resotto/postgres:1.0
#    build: db/
    image: resotto/postgres:1.0
    container_name: postgres
    tty: true
    networks:
      app_net:
        ipv4_address: 172.16.1.2

networks:
  app_net:
    ipam:
      driver: default
      config:
       - subnet: "172.16.1.0/24"

DB

使い捨てアプリのため、postgresサービスのDockerfileから呼び出すstartup.sh内にスキーマを定義していました。

要素テーブルとその関連テーブルの2つを定義しました。

要素テーブルは(恥ずかしいのですが)テーブル名が残念なのと、typeカラムは数字で種類の情報を保持しているため、アンチパターンです。

element_typesテーブルを定義し、そちらへの外部キー制約をつけるのが良いです。

関連テーブルは、要素テーブルの主キーidの親と子で複合主キーになっています。

#!/bin/bash
service postgresql-9.6 start
psql -U postgres -c "create role uranus superuser login"
createdb -U postgres -O uranus uranusdb
psql -U uranus uranusdb -c \
"create table mst_element ( \
  id serial PRIMARY KEY, \
  type integer, \
  title text, \
  is_checked boolean, \
  is_root boolean, \
  create_date date, \
  update_date date \
);"

psql -U uranus uranusdb -c \
"create table mst_relation ( \
  parent_id integer, \
  child_id integer, \
  PRIMARY KEY (parent_id, child_id) \
);"

/bin/bash

フロントエンド

(画面の実装は採用する技術次第ですが)再帰的に表示する部分を独立して実装しました。

<!-- index.jsp -->
<!DOCTYPE html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>Uranus</title>
        <link rel="stylesheet" href="${f:url('/css/index.css')}">
        <noscript>
            <link rel="stylesheet" href="${f:url('/css/noscript.css')}">
        </noscript>
    </head>
    <body>
        <!-- header -->
        <h1>
            <c:choose>
                <c:when test="${mode eq 0}">
                    [ Memo ] /
                    <s:link href="task">Task</s:link>
                </c:when>
                <c:otherwise>
                    <s:link href="memo">Memo</s:link>
                    / [ Task ]
                </c:otherwise>
            </c:choose>
        </h1>
        <p>
            <!-- create button -->
            <input type="button" onclick="toggleCreateMode()" value="new" class="btn createButton">
            <!-- clear section -->
            <div id="clearButton">
                <s:form>
                    <input type="submit" name="clear" value="clear" class="btn">
                    <input type="hidden" name="mode" value="${mode}">
                </s:form>
            </div>
        </p>
        <!-- create block -->
        <div id="createBlock">
            <s:form>
                <textarea name="inputText" rows="3" cols="30"></textarea>
                <input type="submit" name="create" value="create" class="btn">
                <input type="hidden" name="mode" value="${mode}">
            </s:form>
        </div>
        <!-- main contents -->
        <ul>
            <c:forEach var="elm" items="${list}" varStatus="parentStatus">
                <c:if test="${mode eq 1}">
                    <c:if test="${elm.updateDate.toString() != date}">
                        <p>
                            <h2>
                                ${elm.updateDate}
                            </h2>
                            <c:set var="date" value="${elm.updateDate.toString()}" scope="request"></c:set>
                        </p>
                    </c:if>
                </c:if>
                <c:set var="child" value="${elm}" scope="request"></c:set>
                <c:import url="element.jsp"></c:import>
                <br>
            </c:forEach>
        </ul>
        <script type="text/javascript" src="${f:url('/js/index.js')}"></script>
    </body>
</html>
<!-- element.jsp -->
<li>
    <c:if test="${mode eq 1}">
        <input type="checkbox" id="checkbox_${child.id}" onclick="toggleCheckbox(this)" ${child.isChecked ? "checked" : ""}>
    </c:if>
    <p>
        <c:if test="${child.isChecked}"><del></c:if>
            ${child.title}
        <c:if test="${child.isChecked}"></del></c:if>
        &nbsp;
    </p>
    <p>
        <input type="button" onclick="toggleAddMode(this)" value="+" class="btn addButton">
    </p>
    <p>
        <input type="button" onclick="toggleEditMode(this)" value="edit" class="btn editButton">
    </p>
    <c:if test="${child.isRoot == false}">
        <s:form>
            <input type="submit" name="up" value="↑" class="btn">
            <input type="hidden" name="targetId" value="${child.id}">
            <input type="hidden" name="mode" value="${mode}">
        </s:form>
    </c:if>
    <s:form>
        <input type="submit" name="remove" value="-" class="btn">
        <input type="hidden" name="targetId" value="${child.id}">
        <input type="hidden" name="mode" value="${mode}">
    </s:form>
    <div class="addBlock">
        <s:form>
            <textarea name="addText" rows="3" cols="30"></textarea>
            <input type="submit" name="add" value="add" class="btn">
            <input type="hidden" name="targetId" value="${child.id}">
            <input type="hidden" name="mode" value="${mode}">
        </s:form>
    </div>
    <div class="editBlock">
        <s:form>
            <textarea name="editText" rows="3" cols="30">${child.title}</textarea>
            <input type="submit" name="update" value="update" class="btn">
            <input type="hidden" name="targetId" value="${child.id}">
            <input type="hidden" name="mode" value="${mode}">
        </s:form>
    </div>
    <ul>
        <c:if test="${child.children != null}">
            <c:forEach var="child" items="${child.children}" varStatus="childStatus">
                <c:set var="child" value="${child}" scope="request"></c:set>
                <c:import url="element.jsp"></c:import>
            </c:forEach>
        </c:if>
    </ul>
</li>

UIUX

(変数を定数っぽく書いたりと迷走してますが)非同期で打ち消し線を入れたり、他の領域をクリックすることによる非表示を実装しています。

// index.js
let createMode = false;
let addMode = false;
let editMode = false;

const toggleCreateMode = () => {
    createMode = !createMode;
    const CREATE_BLOCK = document.getElementById("createBlock");
    if (createMode) {
        CREATE_BLOCK.style.display = "block";
    } else {
        CREATE_BLOCK.style.display = "none";
    }
}

const toggleAddMode = (elm) => {
    const ID = elm.id.split("_")[1];
    addMode = !addMode;
    const ADD_BLOCK = document.getElementById("addBlock_" + ID);
    if (addMode) {
        ADD_BLOCK.style.display = "block";
    } else {
        ADD_BLOCK.style.display = "none";
    }
};

const toggleEditMode = (elm) => {
    const ID = elm.id.split("_")[1];
    editMode = !editMode;
    const EDIT_BLOCK = document.getElementById("editBlock_" + ID);
    if (editMode) {
        EDIT_BLOCK.style.display = "block";
    } else {
        EDIT_BLOCK.style.display = "none";
    }
};

const toggleCheckbox = (elm) => {
    const XHR = new XMLHttpRequest();
    const FD = new FormData();
    const ID = elm.id.split("_")[1];
    const URL = "http://" + location.host
        + location.pathname.match(/\/.+\//) + "toggleCheck";
    FD.append("targetId", ID);
    XHR.open("POST", URL);
    XHR.send(FD);
}

const setEventListener = (selector) => {
    const ELM = document.getElementById(selector);
    if (selector == "createBlock") {
        document.addEventListener('click', function(e) {
            if (!e.target.closest(".createButton")
                    && !e.target.closest("#createBlock")) {
                createMode = false;
                ELM.style.display = "none";
            }
        }, false)
        return;
    }
    const CLASSNAME = selector.split("_")[0];
    if (CLASSNAME == "addBlock") {
        document.addEventListener('click', function(e) {
            if (!e.target.closest(".addButton")
                    && !e.target.closest("#" + selector)) {
                addMode = false;
                ELM.style.display = "none";
            }
        }, false)
    } else {
        document.addEventListener('click', function(e) {
            if (!e.target.closest(".editButton")
                    && !e.target.closest("#" + selector)) {
                editMode = false;
                ELM.style.display = "none";
            }
        }, false)
    }
}

const setId = (str, settingListener) => {
    const LIST = document.getElementsByClassName(str);
    for (let i = 0; i < LIST.length; i++) {
        const ID = str + "_" + i;
        LIST[i].setAttribute("id", ID);
        if (settingListener) {
            setEventListener(ID);
        }
    }
}

const setIndex = () => {
    setEventListener("createBlock");
    setId("addButton", false);
    setId("addBlock", true);
    setId("editButton", false);
    setId("editBlock", true);
}

window.onload = function() {
    setIndex();
}

バックエンド

(Webフレームワークにもよりますが)リクエストを受け取って必要な(ロジック)サービスを呼び出して処理を行っていました。

要素の入れ替えロジックの実装が大変でした。

package com.uranus.service;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.apache.log4j.Logger;
import org.dbflute.cbean.result.ListResultBean;
import org.dbflute.exception.EntityAlreadyDeletedException;
import org.dbflute.optional.OptionalEntity;

import com.uranus.dbflute.exbhv.MstElementBhv;
import com.uranus.dbflute.exbhv.MstRelationBhv;
import com.uranus.dbflute.exentity.MstElement;
import com.uranus.dbflute.exentity.MstRelation;
import com.uranus.dto.ElementDto;
import com.uranus.dxo.ElementDxo;
import com.uranus.util.MstElementDateComparator;
import com.uranus.util.MstElementIdComparator;
import com.uranus.util.StringUtil;
import com.uranus.util.Type;

public class IndexService {

    @Resource
    protected MstElementBhv mstElementBhv;

    @Resource
    protected MstRelationBhv mstRelationBhv;

    /**
     * Parent ElementDto
     */
    private static ElementDto parent;

    /**
     * Log4j logger
     */
    public Logger logger = Logger.getLogger(IndexService.class);

    // -------------------------- public methods --------------------------

    /**
     * Setup ElementDto and return it.
     * @param  text title
     * @return      assembled ElementDto
     */
    public ElementDto assembleElementDto(String text, String mode) {
        ElementDto dto = new ElementDto();
        dto.type = Integer.parseInt(mode);
        dto.title = StringUtil.sanitize(text);
        LocalDate now = LocalDate.now();
        dto.createDate = now;
        dto.updateDate = now;
        return dto;
    }

    /**
     * Get root elements and their children (recursively) from database.
     * @param  type element type
     * @return      contents list if exists, otherwise {@code null}
     */
    public List<ElementDto> getList(int type) {
        List<Integer> rootIdList = getRootElementsId(type);
        if (rootIdList.size() == 0) return null;
        Map<Integer, ElementDto> elmMap = createElementMap(rootIdList);
        ListResultBean<MstRelation> rootRels = getRootRelation(rootIdList);
        rootRels.forEach(rel -> {
            ElementDto parentDto = getParent(elmMap.get(rel.getParentId()));
            setChildren(parentDto, rel.getChildId());
        });
        return createSortedList(elmMap, type);
    }

    /**
     * Create new element.
     * @param dto target ElementDto
     */
    public void createElement(ElementDto dto) {
        insertElement(dto, true);
    }

    /**
     * Add element.
     * @param parentId parent element id
     * @param childDto child ElementDto
     */
    public void addElement(int parentId, ElementDto childDto) {
        MstElement element = insertElement(childDto, false);
        insertRelation(parentId, element.getId());
    }

    /**
     * Update element.
     * @param targetId target element id
     * @param text     element text
     */
    public void updateElementText(int targetId, String text) {
        ElementDto dto = getElementDtoById(targetId);
        dto.title = text;
        dto.updateDate = LocalDate.now();
        MstElement element = ElementDxo.toElementEntity(dto);
        updateElement(element);
    }

    /**
     * Remove element and its relation (recursively) from database.
     * @param id target element id
     */
    public void removeElement(int id) {
        MstElement element = getElementEntityById(id);
        if (element == null) return;
        MstRelation upwardRel = getUpwardRelation(element.getId());
        if (upwardRel != null) deleteRelation(upwardRel);
        removeDownwardContents(element.getId());
    }

    /**
     * Toggle element's isChecked property.
     * @param id target element id
     */
    public void toggleElementCheck(int id) {
        MstElement element = getElementEntityById(id);
        if (element == null) return;
        boolean checked = element.getIsChecked();
        element.setIsChecked(!checked);
        updateElement(element);
    }

    /**
     * Exchange elements relation.
     * @param childId target element id to be promoted
     */
    public void exchangeElements(int childId) {
        if (getElementDtoById(childId).isRoot) return;
        int parentId = getUpwardRelation(childId).getParentId();
        Integer ancestorId = getAncestorId(parentId);
        rearrangeAncestorRelation(ancestorId, parentId, childId);
        ListResultBean<MstRelation> childRels = getRelationByParentId(childId);
        ListResultBean<MstRelation> parentRels = getRelationByParentId(parentId);
        rearrangeChildRelation(parentId, childId, childRels);
        rearrangeParentRelation(parentId, childId, parentRels);
        toggleIsRootProperty(childId, parentId);
    }

    /**
     * Delete all elements and relations with type.
     * @param type contents type
     */
    public void clear(int type) {
        List<Integer> rootElmsIdList = getRootElementsId(type);
        rootElmsIdList.forEach(rootElm -> {
            removeDownwardContents(rootElm);
        });
    }

    // -------------------------- private methods --------------------------

    /**
     * Get ancestor element id.
     * @param  parentId parent element id
     * @return          ancestor element id if exists, otherwise {@code null}
     */
    private Integer getAncestorId(int parentId) {
        MstRelation rel = getUpwardRelation(parentId);
        if (rel != null) return rel.getParentId();
        return null;
    }

    /**
     * Rearrange relation between ancestor element, parent element, and target
     * element.
     * @param ancestorId ancestor element id
     * @param parentId   parent element id
     * @param targetId   target element id to be promoted
     */
    private void rearrangeAncestorRelation(Integer ancestorId, int parentId, int targetId) {
        if (ancestorId != null) {
            deleteRelation(ancestorId, parentId);
            insertRelation(ancestorId, targetId);
        }
    }

    /**
     * Rearrange relation between parent element and target element.
     * @param parentId   parent element id
     * @param targetId   target element id to be promoted
     * @param parentRels parent element's downward relation
     */
    private void rearrangeParentRelation(int parentId, int targetId, ListResultBean<MstRelation> parentRels) {
        removeRelations(parentId, parentRels);
        createRelations(targetId, parentRels);
        insertRelation(targetId, parentId);
    }

    /**
     * Rearrange target element downward relation.
     * @param parentId  parent element id
     * @param targetId  target element id to be promoted
     * @param childRels target element's downward relation
     */
    private void rearrangeChildRelation(int parentId, int targetId, ListResultBean<MstRelation> childRels) {
        if (childRels.size() > 0) {
            removeRelations(targetId, childRels);
            createRelations(parentId, childRels);
        }
    }

    /**
     * Get downward relation by parent id.
     * @param  parentId parent element id
     * @return          relation list
     */
    private ListResultBean<MstRelation> getRelationByParentId(int parentId) {
        ListResultBean<MstRelation> rels = mstRelationBhv.selectList(cb -> {
            cb.query().setParentId_Equal(parentId);
        });
        return rels;
    }

    /**
     * Toggle element is_root property.
     * @param newParentId element id to be promoted
     * @param oldParentId element id to be demoted
     */
    private void toggleIsRootProperty(int newParentId, int oldParentId) {
        MstElement oldParent = getElementEntityById(oldParentId);
        if (oldParent.getIsRoot()) {
            oldParent.setIsRoot(false);
            updateElement(oldParent);
            MstElement newParent = getElementEntityById(newParentId);
            newParent.setIsRoot(true);
            updateElement(newParent);
        }
    }

    /**
     * Create relation with parentId and relation list's childId.
     * @param parentId parent element id
     * @param rels     relation list
     */
    private void createRelations(int parentId, ListResultBean<MstRelation> rels) {
        rels.forEach(rel -> {
            if (parentId != rel.getChildId()) {
                insertRelation(parentId, rel.getChildId());
            }
        });
    }

    /**
     * Remove relation with parentId and relation list's childId.
     * @param parentId
     * @param rels
     */
    private void removeRelations(int parentId, ListResultBean<MstRelation> rels) {
        rels.forEach(rel -> {
            deleteRelation(parentId, rel.getChildId());
        });
    }

    /**
     * Create map from root elements id.
     * @param  rootIdList root elements id
     * @return            map key:element id, value:ElementDto
     */
    private Map<Integer, ElementDto> createElementMap(List<Integer> rootIdList) {
        Map<Integer, ElementDto> elmMap = new HashMap<>();
        ListResultBean<MstElement> rootElms = getRootElement(rootIdList);
        rootElms.forEach(rootElm -> {
            elmMap.put(rootElm.getId(), ElementDxo.toElementDto(rootElm));
        });
        return elmMap;
    }

    /**
     * Create list from map.
     * @param  map  key:id, value:ElementDto
     * @param  type contents type
     * @return      ElementDto list
     */
    private List<ElementDto> createSortedList(Map<Integer, ElementDto> map, int type) {
        List<ElementDto> list = new ArrayList<>(map.values());
        if (type == Type.MEMO.getType()) {
            list.sort(new MstElementIdComparator());
        } else {
            list.sort(new MstElementDateComparator());
        }
        return list;
    }

    /**
     * Get root elements by root elements id.
     * @param  rootIdList root elements id list
     * @return            root elements list
     */
    private ListResultBean<MstElement> getRootElement(List<Integer> rootIdList) {
        ListResultBean<MstElement> rootElms = mstElementBhv.selectList(cb -> {
            cb.query().setId_InScope(rootIdList);
            cb.query().addOrderBy_Id_Asc();
        });
        return rootElms;
    }

    /**
     * Get root relation by root elements id.
     * @param  rootIdList root elements id list
     * @return            root relation list
     */
    private ListResultBean<MstRelation> getRootRelation(List<Integer> rootIdList) {
        ListResultBean<MstRelation> rootRels = mstRelationBhv.selectList(cb -> {
            cb.query().setParentId_InScope(rootIdList);
            cb.query().addOrderBy_ParentId_Asc();
        });
        logger.info("    Root relation list: " + rootRels);
        return rootRels;
    }

    /**
     * Delete downward relation and its element recursively from database.
     * @param parentId parent element id
     */
    private void removeDownwardContents(int parentId) {
        List<MstRelation> downwardRels = getAllRelation(parentId);
        downwardRels.forEach(rel -> {
            removeDownwardContents(rel.getChildId());
            deleteRelation(parentId, rel.getChildId());
        });
        MstElement elm = getElementEntityById(parentId);
        deleteElement(elm);
    }

    /**
     * Update element.
     * @param elm element entity
     */
    private void updateElement(MstElement elm) {
        mstElementBhv.update(elm);
        logger.info("    Element updated: " + elm);
    }

    /**
     * Delete element from database by element entity.
     * @param  elm element entity
     * @return     element entity after deletion.
     */
    private MstElement deleteElement(MstElement elm) {
        mstElementBhv.delete(elm);
        logger.info("    Element deleted: " + elm);
        return elm;
    }

    /**
     * Delete relation from database by parentId and childId.
     * @param  parentId parent element id.
     * @param  childId  child element id.
     * @return          relation entity after deletion.
     */
    private MstRelation deleteRelation(int parentId, int childId) {
        MstRelation rel = new MstRelation();
        rel.setParentId(parentId);
        rel.setChildId(childId);
        return deleteRelation(rel);
    }

    /**
     * Delete relation from database by relation entity.
     * @param  rel relation entity
     * @return     relation entity after deletion.
     */
    private MstRelation deleteRelation(MstRelation rel) {
        try {
            mstRelationBhv.delete(rel);
        } catch (EntityAlreadyDeletedException e) {}
        logger.info("    Relation deleted: " + rel);
        return rel;
    }

    /**
     * Get all relation by id.
     * @param  parentId parent element id
     * @return          relation entity list
     */
    private ListResultBean<MstRelation> getAllRelation(int parentId) {
        ListResultBean<MstRelation> rels = mstRelationBhv.selectList(cb -> {
            cb.query().setParentId_Equal(parentId);
            cb.query().addOrderBy_ChildId_Asc();
        });
        return rels;
    }

    /**
     * Insert Element into database.
     * @param  dto    target element DTO
     * @param  isRoot whether argument DTO is root or not
     * @return        element entity after insertion
     */
    private MstElement insertElement(ElementDto dto, boolean isRoot) {
        MstElement element = ElementDxo.toElementEntity(dto);
        element.setId(null);
        element.setIsRoot(isRoot);
        mstElementBhv.insert(element);
        logger.info("    Element created: " + element);
        return element;
    }

    /**
     * Insert Relation into database.
     * @param  parentId parent element id
     * @param  childId  child element id
     * @return          relation entity after insertion.
     */
    private MstRelation insertRelation(int parentId, int childId) {
        MstRelation relation = new MstRelation();
        relation.setParentId(parentId);
        relation.setChildId(childId);
        mstRelationBhv.insert(relation);
        logger.info("    Relation created: " + relation);
        return relation;
    }

    /**
     * Get element from database by id.
     * @param  id element id
     * @return    ElementDto if entity exists, {@code null} otherwise
     */
    private ElementDto getElementDtoById(int id) {
        OptionalEntity<MstElement> op = mstElementBhv.selectEntity(cb -> {
            cb.query().setId_Equal(id);
        });
        if (op.isPresent()) return ElementDxo.toElementDto(op.get());
        return null;
    }

    /**
     * Get element from database by id.
     * @param  id element id
     * @return    element entity if it exists, otherwise {@code null}
     */
    private MstElement getElementEntityById(int id) {
        OptionalEntity<MstElement> op = mstElementBhv.selectEntity(cb -> {
            cb.query().setId_Equal(id);
        });
        if (op.isPresent()) return op.get();
        return null;
    }

    /**
     * Get relation from database by id.
     * @param  childId child element id
     * @return         relation entity if it exists, {@code null} otherwise
     */
    private MstRelation getUpwardRelation(int childId) {
        OptionalEntity<MstRelation> op = mstRelationBhv.selectEntity(cb -> {
            cb.query().setChildId_Equal(childId);
        });
        if (op.isPresent()) return op.get();
        return null;
    }

    /**
     * If parent has children, set it recursively by id.
     * @param parentDto parent ElementDto
     * @param childId   child element id
     */
    private void setChildren(ElementDto parentDto, int childId) {
        if (parentDto == null) return;
        ElementDto childDto = getElementDtoById(childId);
        if (childDto == null) return;
        // If child also has children, set it recursively
        setChildrenRecursively(childDto, childId);
        parentDto.children.add(childDto);
    }

    /**
     * Get relation with childId and call setChildren if they exist
     * @param childDto child ElementDto
     * @param childId  child element id
     */
    private void setChildrenRecursively(ElementDto childDto, int childId) {
        ListResultBean<MstRelation> rels = mstRelationBhv.selectList(cb -> {
            cb.query().setParentId_Equal(childId);
            cb.query().addOrderBy_ChildId_Asc();
        });
        rels.forEach(rel -> {
            setChildren(childDto, rel.getChildId());
        });
    }

    /**
     * Get root elements from database, and return their id.
     * @param  mode contents mode
     * @return      root elements id list
     */
    private List<Integer> getRootElementsId(int type) {
        ListResultBean<MstElement> elms = getRootElements(type);
        List<Integer> idList = new ArrayList<>();
        elms.forEach(elm -> {
            idList.add(elm.getId());
        });
        logger.info("    Root id list: " + idList);
        return idList;
    }

    /**
     * Get root elements with element type.
     * @param  type element type
     * @return      Element entity list
     */
    private ListResultBean<MstElement> getRootElements(int type) {
        return mstElementBhv.selectList(cb -> {
            cb.query().setIsRoot_Equal(true);
            cb.query().setType_Equal(type);
            cb.query().addOrderBy_Id_Asc();
        });
    }

    /**
     * Return the same ElementDto until argument DTO has different id.
     * @param  dto ElementDto
     * @return     parent if argument DTO has the same id as that of it,
     *                 argument DTO otherwise.
     */
    private ElementDto getParent(ElementDto dto) {
        if (parent != null && dto.id == parent.id.intValue()) return parent;
        parent = dto;
        return parent;
    }
}

所感

使用するテーブルはたったの二つだけですが、一方でアプリケーションロジックの実装がかなり大変でした。

もし同じ仕様を実装するなら

  • ドメイン駆動設計(注:完全な好み)
  • Compositeパターン
  • 閉包テーブル

等を使ってみるといいかもしれません。

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

Docker-in-dockerでIs the docker daemon running?が出たときの対処法

背景

GitLabCIの勉強を「GitLab実践ガイド(著:北山 晋吾さん)」を使って行っていました。
せっかくだからDockerをDocker Executer上で使用するためにGitLabが推奨するDocker-in-docker(以下dind)を使ってみようと思い試していたところタイトルにあるエラーに遭遇しました。

 $ docker build . -t ${APP_NAME}
 Cannot connect to the Docker daemon at tcp://docker:2375. Is the docker daemon running?
 ERROR: Job failed: exit code 1

対処法を調べましたが、日本語の情報はなかったので書き置きします。

GitLab実践ガイド(著:北山 晋吾さん)はこちら。
Kindle Unlimitedの専門書ラインナップが充実してきて嬉しいです。
https://www.amazon.co.jp/dp/B079DL362C/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1

環境

  • GitLab動作環境 : GitLab.com
  • Runner動作環境 : Ubuntu16.04(parallels Desktop15)

GitLab.com上にテスト用プロジェクトを作成し、Specific Runnerを仮想デスクトップ環境のUbuntu上に作成しています。
dindを使用するため、Docker Executerを設定します。

dindについてはこちらの記事がわかりやすかったです。
Dockerコンテナ内からDockerを使うことについて

なお、GitLab.comのShared Runnersを使用したところエラーは発生しませんでした。
おそらく適切に対処されているのでShared Runnersを使用するのも解決策となります。
そのため本記事は自前でRunnerを構築する人向けのものになります。

エラー時の設定

dindを利用するために、gitlab-ci.ymlファイルに以下の設定を行います。

DOCKER_DRIVERにoverlayFSを選択しているのはおすすめ設定だから入れておこうの精神です。
docker infoを使用して、Strage Driverを確認したところ明示的に宣言しなくてもデフォルトでoverlay2になっていたため不要かもしれません。
この機会に以下の記事で勉強したのでご紹介だけしておきます。
Dockerのストレージドライバを理解する

.gitlab-ci.yml
# docker image
image: docker:latest

# Docker DriverをoverlayFSに変更する(おすすめ設定)。
variables:
  DOCKER_DRIVER: overlay2
# dindを設定
services:
  - docker:dind

この設定でdockerのbuildを行おうとすると"Is the docker daemon running?"が発生するようです。

原因

docker 19.03からdindが自動的にTLS証明書を作成し通信に使用するようになったことが原因のようです。
詳しくは以下の公式ドキュメントに書いています。
対処法も書いていますが後述で私が試した対処法を書いていきます。

https://about.gitlab.com/releases/2019/07/31/docker-in-docker-with-docker-19-dot-03/

飛び元はこちら
https://docs.gitlab.com/ee/ci/docker/using_docker_build.html#use-docker-in-ocker-workflow-with-docker-executor

元のissueはこちら
https://gitlab.com/gitlab-org/gitlab-runner/issues/1986

対処法

対処法1: TLSを構築する

こちらも公式にある方法(Configure TLS)です。
サービスとジョブコンテナ間で証明書を共有するため、volumesにマウントを追加します。
他の対処法はTLSを使用しないようにしているため、一番正当な対処法のように思います。
ちなみにconfig.tomlはubuntuだと'sudo vi /etc/gitlab-runner/config.toml'で編集できます。

config.toml
[[runners]]
  name = "My Docker Runner"
  url = "http://gitlab.com"
  token = ""
  executor = "docker"
  [runners.custom_build_dir]
  [runners.docker]
    privileged = true
    volumes = ["/certs/client", "/cache"]
    shm_size = 0
.gitlab-ci.yml
image: docker:19.03.0

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"

services:
  - docker:19.03.0-dind

対処法2: dindのバージョン変更

dind 19.03からの不具合のため、単にバージョンを下げることで解決します。

.gitlab-ci.yml
image: docker:18.09.7

# Docker DriverをoverlayFSに変更する(おすすめ設定)。
variables:
  DOCKER_DRIVER: overlay2
# dindを設定
services:
  - docker:18.09.7-dind

対処法3: TLSを使用しない

こちらも公式にある方法(Disable TLS)です。
TLS証明書を明示的に使用しないように設定すれば回避することができます。

.gitlab-ci.yml
image: docker:19.03.0

variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: ""

services:
  - docker:19.03.0-dind

まとめ

'Is the docker deamon running?'の対処法をまとめました。
基本的に社内利用の場合はどの方法でも良いかなと思いますが、一番おすすめされている方法は対処法1のTLS構築です。

専門外な上に、記事初投稿なので至らぬ点があったらご指摘願います。

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

GitHub Packagesに公開したDockerイメージをpullする

GitHub ActionsとGitHub PackagesでDockerのイメージを作ってpushしたので、いざpullして使おうとしたら以下のようなエラーが…

Error response from daemon: Get https://docker.pkg.github.com/v2/freeesia/vivideo/server/manifests/latest: no basic auth credentials

publicリポジトリなのに認証が必要…?と思って調べたら、どうも今はpublicリポジトリのイメージであれ認証が必要なようです。
https://github.community/t5/GitHub-Actions/docker-pull-from-public-GitHub-Package-Registry-fail-with-quot/td-p/32782

認証のためにはまずPersonal access tokensからトークンを生成します。

image.png
必要な権限はパッケージを取得するだけなので、read:packagesにチェックを入れて生成します。

あとはイメージを起動するマシンでdocker loginします

docker login -u <GitHubのユーザー名> -p <上記で生成したトークン> docker.pkg.github.com

以上で無事pullできるようになりました。

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

MacにWindows10をデュアルブート&WS2導入までの参考リンク集

macbookでBootCampを使ってwindows10をデュアルブートする時、
ナチュラルスクロールなどOSXと同じ使い心地を追求してセットアップしたかった。
色んなリンクを辿って「大体この辺見れば一通りOK」みたいなリンク集ができたので、半ば自分用にメモ。

環境

Macbook Pro(2014) 15インチ
OSX Catalina
CPU: Intel core i7-4770HQ @2.20GHz

リンク集

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

Docker(compose)にDjango開発環境を移行する際の備忘録

Dockerfile

FROM python:3.7
ENV PYTHONUNBUFFERED 1
WORKDIR /code
COPY requirements.txt /code/
RUN pip install --no-cache-dir -r requirements.txt
COPY ./code /code/.
RUN python manage.py migrate
  • FROMでpython3.7環境を使用
  • ENVでエラー出力抑制(らしい)
  • WORKDIRで一時作業フォルダを指定
  • WORKDIRにDockerfileと同じディレクトリに配置しているrequirements.txtをコピーする
  • RUNでrequirements.txtにしたがってインストールを行う
  • COPYでmanage.pyがあるディレクトリを丸ごとWORKDIR配下にコピー
  • RUNでmigrateしておく

ハマりポイントとしては、WORKDIRへのファイルコピー。
Dockerfileと同じディレクトリにあるcodeディレクトリ(Djangoプロジェクト)を指定できずFile Not Foundを連発。
前提条件として、Dockerfileとrequirements.txt、codeディレクトリはすべて同じ階層に配置している。

docker-compose.yml

docker-compose.yml
version: '3'
services:
        web:
                build: .
                ports:
                        - "8000:8000"
                volumes:
                        - ./code:/code
                command: python3 manage.py runserver 0.0.0.0:8000
  • versionは3を指定
  • web:は一時的な名称として指定。
  • build:でDockerfileの位置を指定。カレントディレクトリにあるので'.'を指定。
  • ports:でDjangoのポートを指定。
  • volumes:でカレントディレクトリにあるDjangoプロジェクトのディレクトリをコンテナ内の/codeにマウントする。マウントすることでvscodeなどから直接編集した内容がコンテナ上に反映される。
  • command:でDjangoのWebサーバーを起動させる。引数のIPアドレスとポート番号は必要ないかもしれない。

コマンド類

Dockerコマンドをsudoなしで実行させる場合

$ sudo usermod -aG docker ユーザー名
$ newgrp docker

newgrp dockerを.bashrcなどに書いておくと楽になるかもしれない。

イメージの削除

$ docker images
$ docker rmi イメージID先頭3桁

割り当てられているコンテナが存在する場合は-fをつけて強制的に削除する。

$ docker rmi -f イメージID先頭3桁

コンテナの削除

$ docker rm コンテナID

exited状態のコンテナ複数を一度に削除する

$ docker rm $(docker ps -a --filter 'status=exited' -q)

コンテナにログインしてコマンドを実行したい場合

$ docker exec -it コンテナ名 /bin/bash
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Alpine Linux(aarch64)に Docker & docker-compose をインストールする方法

1. はじめに

今回は、自分自身への備忘録を兼ねて、Raspberry Pi にインストールしたAlpine Linux(aarch64)上に Docker & docker-compose をインストールする方法を、お伝えしたいと思います。

2. この記事を読んでできること

  • Alpine Linux(aarch64)で Docker & docker-compose を使えるようになる。

3. 注意事項

  • 特になし。

4. 必要なもの

  • Alpine Linux(aarch64)が動作しているホストOS環境(この記事では Raspberry Pi3B + Alpine Linux(aarch64)を利用)
  • インターネット環境

5. リポジトリの追加登録

  • dockerをインストールできるようにするために、/etc/apk/repositoriesファイルにcommunityリポジトリを追加する。
  • docker-composeをインストールできるようにするために、/etc/apk/repositoriesファイルにtestingリポジトリを追加する。
  • 実際には、該当箇所をコメントアウト(#)を解除するだけでOKです。
コマンドプロンプト
// リポジトリの追加登録
# vi /etc/apk/repositories

===
http://dl-cdn.alpinelinux.org/alpine/v3.11/main
#http://dl-cdn.alpinelinux.org/alpine/v3.11/community
#http://dl-cdn.alpinelinux.org/alpine/edge/main
http://dl-cdn.alpinelinux.org/alpine/edge/community
http://dl-cdn.alpinelinux.org/alpine/edge/testing
===

6. dockerとdocker-composeのインストール

  • Dockerをインストールします。
  • Dockerだけの利用でよければ、以下のコマンドだけで完了です。
コマンドプロンプト
// インストール
# apk add docker docker-compose

// dockerのバージョン確認
# docker -v
Docker version 19.03.6, build 369ce74a3ce86a392e39e45d3960ce970fdfac97

// docker-composのバージョン確認
# docker-compose -v
docker-compose version 1.25.2, build unknown

// dockerの起動
# service docker start

// dockerの自動起動有効化
# rc-update add docker boot

7. dockerの動作確認

コマンドプロンプト
// dockerの実行(ハローワールド)
# docker run --rm hello-world

8. docker-composeの動作確認

  • docker-compose.ymlファイルを作成し、ファイル保存する。
docker-compose.yml
version: "2"

services:
  app:
    image: hello-world

  • docker-composeを実行する。
コマンドライン
// docker-compose.ymlファイルが存在することを事前確認
# ls
docker-compose.yml

// docker-composeを実行
# sudo docker-compose up

9. 参考:Dockerコマンド、docker-composeコマンド

10. おわりに

いかがでしたでしょうか?意外と簡単にDocker & docker-composeをはじめることができたのではないかなと思います。
今回の記事が、みなさまの学習の参考になれば幸いです。

2020/02/23 TAKAHIRO NISHIZONO

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

kubernetesのwindows環境構築

windows上でもkubectl(kubernetesのコマンド)が通るようにしたので、
その手順をまとめます。

事前知識

Dockerとは

kubernetesを勉強する前にDokckerについて簡単にまとめます。
Dockerはコンテナを実行するための実行環境およびツールキットです。
Dockerの理解がまだ浅いと思う方は、以下のページが参考になると思います。
https://datawokagaku.com/whatisdocker/
https://y-ohgi.com/introduction-docker/

kubernetesとは

コンテナ化されたアプリケーションのデプロイ、スケーリングなどの管理を自動化するためのプラットフォームです。Docker単体では一定以上の規模の開発において、複数のホストを効率的に管理することが難しいので、kubernetesといったコンテナオーケストレーションエンジンを利用します。これだけでは分かりにくいと思うので、以下のページが参考になりそうです。自分は本で勉強しました。
https://kubernetes.io/ja/
https://book.impress.co.jp/books/1118101055

環境

  • Windows10 pro
  • Docker for windows

windows10 homeだと環境構築の難易度が相当上がるらしいです。
自分はproにアップグレードしました。

手順

Hyper-Vの有効化

まずは、Hyper-V関連の機能を有効にしましょう。

Windowsキーから「Windowsの機能の有効化または無効化」を検索
Hyper-Vにチェックを入れてOK
image.png

docker for windowsをインストール

以下からダウンロードし、インストールしてください。
https://docs.docker.com/docker-for-windows/install/

以下のコマンドが通れば正常にインストールされて起動できています。

$ docker ps

もし右下に常駐しているdokckerアイコン(くじら)が赤くなっていれば、
なんらかの理由でdockerが起動できてないです。(白は正常)
メモリエラーの場合は他に起動しているプログラムを終了するか、
アイコンを右クリック - setting - AdvancedからMemoryを調整すると
起動できるかもしれません。

image.png

kubernetesを有効化

右下に常駐しているdokckerアイコン(くじら)を右クリック
setting → Kubernetes → Enable Kubernetes にチェックを入れてApply
image.png

下のコマンドでノードが表示できれば有効化できています。

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

Unity Accelerator + Prometheus + GrafanaをDockerでサービス化する

はじめに

Unity2019.3からAsset Pipeline v2が追加されてUnity Acceleratorを組み合わせるとインポートしたアセットをキャッシュおよび共有することができます。

それまでのUnityにもAsset Pipeline v1とキャッシュサーバーでキャッシュできていましたが、Unity Acceleratorには以下の違いがあります。

  • キャッシュサーバーと同居できる
  • Collaborateのキャッシュに対応
  • 不要なキャッシュを定期的にクリーンアップ
  • 転送されたバイト数や接続されたエディターなどの情報をPrometheusから利用できるメトリクスで公開

フォーラムの以下のスレッドによるとDockerHubで公開する予定があるようですが、まだないのでUnity AcceleratorとPrometheusとダッシュボードのGrafanaをDockerでサービス化しました。

Dockerでサービス化

Dockerfileおよびdocker-composeは以下にUnity Acceleratorのみのv2とキャッシュサーバーも同時にインストールするv1_v2を用意してます。

https://github.com/shiena/docker-unity-accelerator

Unity Acceleratorのみ

# イメージをビルド
docker-compose -f dokcer-compose.yml -f v2.yml -p v2 build

# サービスを実行
docker-compose -f dokcer-compose.yml -f v2.yml -p v2 up -d

# Unity Accelerator単体で実行
docker-compose -f v2.yml -p v2 up -d

# バージョン確認
docker run --rm unity-accelerator:v2 /opt/Unity/accelerator/unity-accelerator --version

Unity Acceleratorとキャッシュサーバー

# イメージをビルド
docker-compose -f dokcer-compose.yml -f v1_2.yml -p v1_v2 build

# サービスを実行
docker-compose -f dokcer-compose.yml -f v1_2.yml -p v1_v2 up -d

# Unity Acceleratorとキャッシュサーバーのみで実行
docker-compose -f v1_v2.yml -p v1_v2 up -d

# バージョン確認
docker run --rm unity-accelerator:v1_v2 /opt/Unity/accelerator/unity-accelerator --version

サービスを起動するとそれぞれ以下からアクセスできます。

Docker化で工夫したこと

  • Unity AcceleratorをダウンロードするURLからバージョンが分からないのでバージョンを確認する方法を用意しています。
  • キャッシュサーバー追加でインストーラーを非対話モードで実行するとnpmのパスが展開されないバグがあるので対話モードprintfで強引にインストールしています。(バグ報告済み)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

手探りDocker #2

前回の記事の続きになります。
手探りDocker #1

次の記事
手探りDocker #3

サンプルアプリケーションのインストール

前回も参考にしたサイトdocker docs
の以下を選択します。
image.png

以下のGitHubのリポジトリをクローンし、クローンしたフォルダに移動して以下コマンドを打ちます。
https://github.com/dockersamples/example-voting-app
以下コマンドを打って「投票アプリ」を起動?します。
docker-compose up --build
image.png

実行するとPythonのエラー?警告?が出ている。

他にも
image.png

image.png
その他に
image.png
と警告やエラーが出ている・・・

挫折しそう・・・

まず1つ目のエラーから調査していきます。
google翻訳に入れてみたところ、

非推奨:Python 2.7は2020年1月1日にサポートを終了しました。
Python2.7のメンテナンスが終了したため、Pythonをアップグレードしてください。 
pipの将来のバージョンでは、Python 2.7のサポートが廃止される予定です。
 pipでのPython 2サポートの詳細については、
https://pip.pypa.io/en/latest/development/release-process/#python-2-supportを参照してください。

警告っぽいです。学習用なので、このまま進めようと思います。

次に2つ目のエラー
ググってみたところ、OSがMACでないために表示される警告の模様
npm WARN notsup SKIPPING OPTIONAL DEPENDENCY: Unsupported platform for fsevents@1.0.14
警告なので問題ないとの意見がちらほらみられるので、これもこのままとする

3つ目のエラー
訳すと「強制的にあなたが何をしているかを知っていることを願っています」とのこと
全然意味が分からない
調べてみるとnpmのキャッシュをクリアすると解消するらしい。
でもコマンドで打ったのはdocker-compose up --buildだけですから、どうすりゃいいのよって感じです。
警告なのでとりあえず飛ばします。

最後のエラー
example-voting-app_vote_1example-voting-app_result_1のコンテナが開始できていないらしい
以下のような画面がでていた。ドライブ共有が原因っぽい。

image.png

Share itを選択しても解決はしなかった。

今日はここまで・・・

次回

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

dockerでrailsの開発環境を構築する

概要

「Quickstart: Compose and Rails」
https://docs.docker.com/compose/rails/

「もう環境構築で悩まない!Dockerを使ってRails環境構築!」
https://www.youtube.com/watch?v=BZS8AHF3TTo

「DockerでのRuby on Rails環境構築を一つずつ詳解する」
https://qiita.com/daichi41/items/dfea6195cbb7b24f3419

この記事をもとに

  • ruby
  • rails
  • postgres

を設定していきます。途中記事内容だとerrorで

全体図

  • dockerfileにてwebサーバーとdbをそれぞれコンテナビルド
  • rails読み込む用のgemfile、gemfilerockを作成。これをもとに作業ディレクトリにrailsnewされる。
  • $ docker-compose run web rails new
  • docker-compose build
  • docker-compose upで起動
  • docker-compose run web rake db:createでdb作成

つまづきポイント

could not translate host name "db" to address: Name or service not

dbのpassがうまく渡せていなかった?

docker-compose.yml
db:
    image: postgres:9.5.18
    environment:
      POSTGRES_PASSWORD: password

にしてdatabase.ymlを下記に

config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: password
  pool: 5

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test

railsコマンドの打ち方

docker-compose run web rails ~

でrailsコマンドを打てる。

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