20190816のdockerに関する記事は20件です。

Dockerで使うコマンドのメモ

こんにちは、未来電子のインターンでプログラミングを勉強している者です。
今週はDockerについて少し学んだので、Dockerで使われるコマンドをメモします。

Dockerとは

Dockerとは、Docker社が提供している仮想化技術のことです。
Dockerでは、コンテナと呼ばれる仮想環境の中でアプリケーションを操作できます。
コンテナは、ホストマシンのカーネルを共有しており、複数のコンテナを一つのホスト上で同時に動かすことができます。
そのため、Dockerは、ハイパーバイザの上に別々のゲストマシンを置くVirtualBoxとは違い、動作が軽いことが特徴です。

Dockerで使われるコマンド

Dockerでは、コマンドを用いてイメージ(コンテナを操作するための設定をまとめたもの)やコンテナの操作をします。
ここでは、主に用いられるコマンドを挙げていきます(親コマンドのdockerは省略)。

コマンド             意味
login レジストリにログインする
logout レジストリからログアウトする
pull イメージやリポジトリをレジストリから取得する
push イメージやリポジトリをレジストリに追加する
images イメージを一覧表示する
build Dockerfileからイメージをビルド(ベースイメージに機能を付け加える)する
tag イメージにタグをつける
create 新規のコンテナを作る
run 新しいコンテナを起動する
attach 起動しているコンテナを操作する
start コンテナを起動する
stop コンテナを停止する
rm コンテナを削除する
ps コンテナを一覧表示する

まとめ

今回は、Dockerで主に使われるコマンドをまとめてみました。
他にもコマンドはたくさんありますので、勉強したら追加したいと思います。
Dockerについて少し勉強しましたが、まだまだ理解不足な部分が多く、間違って記述している部分もあるかと思います。
間違いがありましたら、訂正したいと思いますので、ご指摘のほどよろしくお願いします。

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

Docker初心者に試して欲しい、VSCodeでDockerを簡単に使って開発する(PythonでHelloworldまで)

Dockerを少しづつ弄ってみてはいたのですが、開発環境として使うには、色々な知識が必要だったり、コマンド打ったりとか、中々初心者には敷居が高かったのですが、今年リリースされたVisualStudioCodeのRemoteという拡張機能を使う事で、Docker内でのデバッグなども簡単にできるようになりました。

難しい事抜きに、とりあえずDokcer使ってみたい!!という人にもおすすめです。

対象

  • とりあえず、Docker触ってみたい
  • Docker使いたいけど、コマンド打つのメンドくさい

事前準備

  • DockerHubアカウント
  • VisualStudioCodeインストール
  • GitHubアカウント

GitHubでリポジトリ作成〜クローン

とりあえず、勉強用でもGitHubでリポジトリ作っちゃうのをおすすめします。
この辺は特に問題ないと思いますので、サクっと作っちゃいましょう。

スクリーンショット 2019-08-16 20.30.32.png

できたら、次はクローンですね。CloneorDownloadからサクっとクローンしちゃいます。
スクリーンショット 2019-08-16 20.33.30.png

ここで、GitHubDesktopをインストールしておくと、クローン〜VSCodeでリポジトリのディレクトリ開くまでがシームレスに行えるので、簡単ですよね。

スクリーンショット 2019-08-16 20.35.45.png

クローンが終わったら、あとは、そのまま開くだけですね。Open in Visual Studio CodeでサクっとVSCode開きましょう。

スクリーンショット 2019-08-16 20.37.18.png

VSCodeでDocker環境構築

次に、VSCodeの拡張機能のRemoteをインストールします。

スクリーンショット 2019-08-16 20.39.38.png

終わったら、左下に><こんなマーク出てるのわかりますか?
そこをクリックします。

スクリーンショット 2019-08-16 20.39.38.png

次に出てきたダイアログの中の一番上のRemote-Containers:Open in Container...を選びます

スクリーンショット 2019-08-16 20.43.28.png

次に出てくるダイアログで、Dockerの環境を選べますので、今回はPython3を選びます。

スクリーンショット 2019-08-16 20.45.50.png

ここで、Dockerコンテナのビルドが走りますので、少し時間がかかります。
ビルドが終わったら、VSCodeの中は、既にDockerに接続されている状態です。
試しに、HelloWorld.pyというファイルを作ってみましょう。

スクリーンショット 2019-08-16 20.50.04.png

ここで、追加でおすすめの拡張機能が、VSCodeのPython拡張機能です。
Pythonの普通のファイルをJupyterNotebookのように使う事ができます。

スクリーンショット 2019-08-16 20.51.51.png

この拡張機能をインストールして、一番最初の行に# %%と書けば、あとはJupyterNotebookのようにShitf+Enterで実行ができるようになります。

スクリーンショット 2019-08-16 21.05.04.png

試しに、Helloworldを書いて、Shift+Enterで実行してみましょうか。
右下に、Jupyterインストールする?って聞いてくると思うので、OKでインストールしちゃいましょう。(ごめんなさいSS撮り忘れました・・・)
Jupyterインストール後、再度実行した結果が下のような感じです。

スクリーンショット 2019-08-16 21.10.45.png

イエイ!!:laughing:
ここまで、コマンド叩く事なく、GUIのみの操作でできましたね。

あとがき

Dockerをチームで使って開発を行いたかったのですが、個人的にコマンドって覚えにくいし、打ち間違うしで、あまり使いたくなかったんですよね。
ですが、Remote拡張機能のおかげで、開発環境の構築が全てGUIで行えるようになったので、とても便利になりました。
リポジトリ作成〜コンテナでの開発開始まで、慣れれば3分もあればできるので、色々な環境の色々な言語でのトライ&エラーがやりやすくなりました。

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

Mono Docker で ASP.NET MVC (.NET Framework 4.7.2)

これはなに?

.NET Framework 4.7.2 でビルドしたASP.NET MVC アプリを、Linux Dockerで動かそうと試した備忘録です。

作戦

  • Monoのイメージは公式のものを利用
  • ASP.NETアプリは fastcgi-mono-server4 で動かす
  • Nginxでホスティングし、fastcgi-mono-server4 とは unix domain socketで通信させる
  • fastcgi-mono-server4 は Supervisorでサービス起動する
  • NginxとMono(fastcgi-mono-server4)のコンテナは分ける
  • Monoのバージョンはlatestではなく、5.18.1.28で検証
  • データベースは今は考えない
完成した docker-compose 一式はこちら(Github)

サンプルアプリ

まずは VisualStudio でサンプルアプリを作ります。
ASP.NET MVCプロジェクトの雛形に用意されているサンプルにちょっとページを追加して、環境変数などを表示するようにしました。
aspinfo_on_vs.png
(どっかで見たことのあるデザインですが・・・)
このアプリを「発行」して、一式をLinuxサーバに転送します。

Mono Dockerイメージの調査

公式のDockerイメージがナンボのもんか知るためにシェルに入って調査します。
※サンプルアプリは、/home/mono-docker/app/aspinfo に配置しています

ホスト
# docker run -v /home/mono-docker/app:/home/monoapp -p 9000:9000 -i -t mono:5.18.1.28 /bin/bash

xsp4

MonoのWebサーバ (xsp と fastcgi-mono-server)は含まれていないようなので、インストールします。
ついでにSupervisor と vim も入れます。

コンテナ
# apt update
# apt -y install mono-xsp4 mono-fastcgi-server4 supervisor vim

簡易サーバ xsp を使ってアプリの動作を確認します。

コンテナ
# cd /home/monoapp/aspinfo
# xsp4
xsp4
Listening on address: 0.0.0.0
Root directory: /home/app/aspinfo
Listening on port: 9000 (non-secure)
Hit Return to stop the server.

この状態でLAN内のWebブラウザから http://Linuxホスト:9000 を叩いてページが出ることを確認します。

Supervisor

次にSupervisorの設定と起動の確認を行います。
実はここで大ハマリしました。

Nginxとunix socket通信を行うには、お互いの実行ユーザがsocketに対して書き込み権限がある必要があります。
普通にSupervisorからfastcgiを起動すると、root所有の755でsocketが作成されるため、Nginxが通信できなくなります。
socketファイルは、DockerfileのCMDで Supervisorを起動したあとに作成されるため、後からパーミッションを変更することもできません。
PHP-FPMのように、socketファイルのパーミッションを指定できればよいのですが、fastcgi-mono-serverにはそんな機能が無いのです。

仕方ないので、socketが作られるディレクトリを777で作成し、Nginxの実行ユーザと同じIdを持つユーザを作成し、fastcgi-mono-server はこのユーザで起動するようにします。
※Nginxのコンテナはいじりたくないので、Mono側がNginxのユーザに合わせるようにしています

Nginx の Docker は、ユーザid が "101"の"nginx"ユーザで実行されるようです

Since 1.17.0, both alpine- and debian-based images variants use the same user and group ids to drop the privileges for worker processes:

$ id
uid=101(nginx) gid=101(nginx) groups=101(nginx)

なのでこれと同じユーザを作ります。

コンテナ
# useradd -u 101 -U -s /sbin/nologin -d /home/monoapp -m nginx



また、socketファイル用ディレクトリを777で作成します。

コンテナ
# mkdir -p /var/run/xsp4
# chmod 777 /var/run/xsp4



これを受けて、fastcgi-mono-server 起動用の設定ファイルを作成します。

コンテナ:/etc/supervisor/conf.d/xsp4.conf
[supervisord]
nodaemon=true

[program:xsp4]
directory=/home/monoapp/aspinfo
environment=MONO_IOMAP=all
command=fastcgi-mono-server4 /applications=/:/home/monoapp/aspinfo/ /filename=/var/run/xsp4/mono-xsp4.socket /socket=unix
user=nginx
autostart=true
autorestart=true



Supervisorの試運転

# supervisord &
[1] 775
/usr/lib/python2.7/dist-packages/supervisor/options.py:298: UserWarning: Supervisord is running as root and it is
 searching for its configuration file in default locations (including its current working directory); you probably
 want to specify a "-c" argument specifying an absolute path to a configuration file for improved security.
  'Supervisord is running as root and it is searching '
2019-08-16 10:17:06,882 CRIT Supervisor running as root (no user in config file)
2019-08-16 10:17:06,884 INFO Included extra file "/etc/supervisor/conf.d/xsp4.conf" during parsing
2019-08-16 10:17:06,916 INFO RPC interface 'supervisor' initialized
2019-08-16 10:17:06,918 CRIT Server 'unix_http_server' running without any HTTP authentication checking
2019-08-16 10:17:06,920 INFO supervisord started with pid 775
2019-08-16 10:17:07,933 INFO spawned: 'xsp4' with pid 778
2019-08-16 10:17:08,942 INFO success: xsp4 entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)

# ps axu
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.3  18152  3248 pts/0    Ss   09:29   0:00 /bin/bash
root       775  0.7  1.9  57920 20212 pts/0    S    10:17   0:00 /usr/bin/python /usr/bin/supervisord
nginx      778  0.5  2.8 293076 28932 pts/0    Sl   10:17   0:00 /usr/bin/mono /usr/lib/mono/4.5/fastcgi-mono-server4.ex
root       783  0.0  0.2  36636  2856 pts/0    R+   10:19   0:00 ps axu

うまく動きました :sunny:

docker-compose

これらを踏まえて、MonoとNginxのコンテナを作成します。
ディレクトリ構成はこんな感じ
directory.png

サンプルアプリも含めた一式はこちら

docker-compose.yml

Nginxは alpine ベースを使ってみました。
イメージのサイズは21.2MBとそこそこ軽量です。(1.17.3時点の値)

docker-compose.yml
version: '3'
services:
  mono:
    build: ./mono
    volumes:
      - ./app:/home/monoapp
      - xsp4socket:/var/run/xsp4
  nginx:
    image: nginx:alpine
    ports:
      - 80:80
    depends_on:
      - mono
    volumes:
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
      - ./nginx/fastcgi_params:/etc/nginx/fastcgi_params
      - ./app:/home/monoapp
      - xsp4socket:/var/run/xsp4
volumes:
  xsp4socket:

mono

調査した操作をDockerfileに記述し、イメージを作成します。
Supervisorの設定ファイルをコピーし、CMDで起動します。

mono/Dockerfile
FROM mono:5.18.1.28

RUN apt update \
    && apt -y install mono-xsp4 mono-fastcgi-server4 supervisor \
    && useradd -u 101 -U -s /sbin/nologin -d /home/monoapp -m nginx \
    && mkdir -p /var/run/xsp4 \
    && chmod 777 /var/run/xsp4 \
    && mkdir -p /etc/mono/registry \
    && chmod 777 /etc/mono/registry

COPY ./xsp4.conf /etc/supervisor/conf.d/

CMD ["/usr/bin/supervisord"]
mono/xsp4.conf
[supervisord]
nodaemon=true

[program:xsp4]
directory=/home/monoapp/aspinfo
environment=MONO_IOMAP=all
command=fastcgi-mono-server4 /applications=/:/home/monoapp/aspinfo/ /filename=/var/run/xsp4/mono-xsp4.socket /socket=unix
user=nginx
autostart=true
autorestart=true

Nginx

サイトの設定ファイル。
fastcgi-mono-server と unix domain socket で通信を行います。

nginx/default.conf
server {
        listen 80;
        root /home/monoapp/aspinfo;
        server_name _;

        location / {
                index index.html index.htm default.aspx Default.aspx;

                fastcgi_pass unix:/var/run/xsp4/mono-xsp4.socket;
                include /etc/nginx/fastcgi_params;
                client_max_body_size 10M;  # upload limit
        }

        # favicon.ico
        location = /favicon.ico {
                log_not_found off;
                access_log off;
        }
}



fastcgi-mono-server用の設定を追記したパラメータファイル

nginx/fastcgi_params
fastcgi_param  QUERY_STRING       $query_string;
fastcgi_param  REQUEST_METHOD     $request_method;
fastcgi_param  CONTENT_TYPE       $content_type;
fastcgi_param  CONTENT_LENGTH     $content_length;

fastcgi_param  SCRIPT_NAME        $fastcgi_script_name;
fastcgi_param  REQUEST_URI        $request_uri;
fastcgi_param  DOCUMENT_URI       $document_uri;
fastcgi_param  DOCUMENT_ROOT      $document_root;
fastcgi_param  SERVER_PROTOCOL    $server_protocol;
fastcgi_param  HTTPS              $https if_not_empty;

fastcgi_param  GATEWAY_INTERFACE  CGI/1.1;
fastcgi_param  SERVER_SOFTWARE    nginx/$nginx_version;

fastcgi_param  REMOTE_ADDR        $remote_addr;
fastcgi_param  REMOTE_PORT        $remote_port;
fastcgi_param  SERVER_ADDR        $server_addr;
fastcgi_param  SERVER_PORT        $server_port;
fastcgi_param  SERVER_NAME        $server_name;


# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param  REDIRECT_STATUS    200;

# for Mono configration
fastcgi_param  PATH_INFO "";
fastcgi_param  SCRIPT_FILENAME    $document_root$fastcgi_script_name;

起動

ホスト
# docker-compose up -d --build



うまく行けば、サンプリアプリが動きます。
ちゃんと Nginx経由 / unix OS で動いていると確認できます :sunny:
aspinfo_on_mono.png

感想

  • ニッチなソリューションなため、情報が少なくて困りました
  • とっとと.NET Coreに移行したいです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Laravel環境を3分で構築する【ついでにDocker入門】

うわあああああ!Laravelが使いたいよおおおお!
だけどサーバー構築面倒くさいよおおおお!!!!

生きてるとそんなときもありますよね。
大丈夫、3分あればできます。

実はもう別の方がQiitaに投稿されているのですが、Docker入門にもちょうどいいので少しだけ多めに解説した記事になります。
Docker で Laravel 環境 を 3分くらいで作る

対象読者

  • Dockerなんて(ほとんど)わかんない人
  • でもとにかく一刻も早くLaravelを使いたい人

上記記事より

これだけです。

DBMSは MariaDB となっているようです。

https://github.com/bitnami/bitnami-docker-laravel

$ mkdir ~/myapp && cd ~/myapp
$ curl -LO https://raw.githubusercontent.com/bitnami/bitnami-docker-laravel/master/docker-compose.yml
$ docker-compose up

解説するよ

何をしているのかを堅苦しくいうと、
「WordPressのローカル構築用ソフトを開発しているBitnamiが公式に提供しているDockerイメージをdocker-composeで一気に準備して起動している」
といったところでしょうか。

素晴らしいことに、この3行だけで本当に仮想Docker環境を手元に用意することができてしまいます。
こちらを少しだけ詳細に解説してみましょう。
※Dockerのインストールだけは先に済ませておいてください

1行目はいいですよね。
適当なフォルダを作って移動するだけです。
次にcurlコマンドでdocker-compose.ymlファイルを入手します。

なんだそれは、と思われた方もいるかもしれません。
中身を見てみましょう。

docker-compose.yml
version: '2'

services:
  mariadb:
    image: 'bitnami/mariadb:10.1'
    environment:
      - ALLOW_EMPTY_PASSWORD=yes
      - MARIADB_USER=my_user
      - MARIADB_DATABASE=my_database
      - MARIADB_PASSWORD=my_password

  myapp:
    tty: true
    image: bitnami/laravel:5-debian-9
    environment:
      - DB_HOST=mariadb
      - DB_USERNAME=my_user
      - DB_DATABASE=my_database
      - DB_PASSWORD=my_password
    depends_on:
      - mariadb
    ports:
      - 3000:3000
    volumes:
      - ./:/app
    # privileged: true # Privileged mode could be required to run this container under Windows

docker-composeは複数のコンテナを一元管理するためのツールです。
詳しくはこちら。
DockerComposeの基本

簡単に言うと、こちらのymlファイルでは"mariadb" と "myapp" 、それぞれの仮想環境(=Dockerコンテナ)を2つ手元に起動しますよ! という起点が定義されています。
"mariadb" がDBサーバ、"myapp" がwebサーバ兼APサーバですね。Laravelに触れてるとこの言葉自体もなかなか聞かなくなりますが……

さて、3行目の

$ docker-compose up

を入力するとお察しの通り念願のDockerコンテナが起動するわけですが、少しお待ちを。
先にこの超便利ツールをインストールしちゃいましょう。
(すでに3分超えてると思いますが、そこは触れないでください)

DockerとDocker ComposeのTerminal UI「lazydocker」のご紹介

これはDockerにあまり触れる機会がない方でもパッと使える素晴らしいツールです。

起動したらこんな感じの画面になります。
スクリーンショット 2019-08-16 20.23.19.png

さて、この状態で例の

$ docker-compose up

を実行してみましょう。
実行!

こうなります。
スクリーンショット 2019-08-16 20.32.04.png

なんか2つrunningの状態になっていますね。
これが先程docker-compose.ymlファイルで確認した2つのコンテナです。
もうこの状態で2つのサーバがローカルで動いているような状態になっています。

このサーバを停止、再起動したりするのもlazydocker上から簡単にできるので、先の記事を参考にしながらぜひやってみてください。

また、「本当にサーバとして起動している」のを実際に中に入って確認してみたい場合、

$ docker exec -it myapp_myapp_1 /bin/bash

で入ってみることができます。
この操作もlazydocker上からできるのですが、macで利用する場合上下ボタンが効かなくなるんですよね……

閑話休題。

さて、いよいよ http://localhost:3000/ にアクセスしてみましょう。
スクリーンショット 2019-08-16 20.36.21.png

Hello, Laravel!
簡単ですね。

実際のソースコードは先程作った"myapp"ディレクトリに展開されています。
ローカルのコードを仮想環境が共有して使っているイメージですね。なので、ローカルのコードをちゃちゃっといじればブラウザ側のLaravelの動作に即座に反映されます。

この方法で構築すれば、デバッグも可能です。
こちらの記事で完璧にまとめてくださっているので、ぜひお読みください!
Dockerで構築したPHP環境をxdebugでデバッグ(vscode)

今回はこのあたりで。

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

DockerでChainer+JupyterLabを構築してみた

機械学習やディープランニングのChainerというフレームワークがあると知ったので、
遊びでDockerで構築してみた。

GitHub

できたやつ
https://github.com/tubutubumustard/chainer_docker.git

Dockerfile

Dockerfile
FROM ubuntu:16.04

WORKDIR /workdir

RUN apt-get update -y && \
    apt-get install -y --no-install-recommends \
    python3-dev \
    python3-pip \
    python3-wheel \
    python3-setuptools \
    git \
    g++ \
    make \
    cmake \
    libblas3 \
    libblas-dev \
    && \
    rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/*

RUN pip3 install --upgrade pip

RUN export CHAINER_BUILD_CHAINERX=1
RUN export CHAINERX_BUILD_CUDA=1

RUN pip3 install -U --no-cache-dir \
    'ideep4py<2.1' \
    cupy-cuda92==6.2.0 \
    chainer==6.2.0 \
    scikit-learn \
    pandas \
    matplotlib \
    jupyterlab

Dockerfileは下記を参考にして作成しました。
https://hub.docker.com/r/chainer/chainer/dockerfile
https://github.com/chainer/chainer/blob/master/docker/intel/python3/Dockerfile

WORKDIR /workdirJupyterLabで作業するためのディレクトリになります。

iDeepCuPyをインストールしていますが、
僕のノートパソコンにはNVIDIAGPUは入ってないので、
iDeepで計算することになります。

DockerCuPyを使用したい場合は、色々設定しないといけないみたいです。
(気が向いたらやってみようかな?)

docker-compose.yml

docker-compose.yml
version: '3'
services:
  chainer:
    build: ./
    volumes:
      - ./workdir:/workdir
    command: jupyter lab --port 8000 --ip=0.0.0.0 --allow-root --NotebookApp.token=''
    ports:
      - 50020:8000

docker-compose.ymlでは、遊び用なので、
JupyterLabrootの起動の許可と、tokenを無効にしています。

コンテナを起動して、JupyterLabにアクセスする

sh
$ docker-compose up -d

ブラウザでアクセスする

スクリーンショット 2019-08-16 20.19.34.png

Chainerのバージョンや実行環境を確認

スクリーンショット 2019-08-16 20.22.14.png

Chainerのチュートリアルを動かしてみる

スクリーンショット 2019-08-16 20.27.02.png

スクリーンショット 2019-08-16 20.28.23.png

スクリーンショット 2019-08-16 20.31.17.png

ちなみにCuPyを使おうとすると、怒られる

スクリーンショット 2019-08-16 20.32.36.png

終わり

本格的に機械学習やディープランニングの練習するなら、専用のサイトが用意しているみたいなので、そっちを使おう!

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

【チートシート】Dockerの使い方

一時コンテナ作成
docker pull イメージ
docker run --rm -it 【コンテナ名指定】/bin/bash (—rmはexit後にコンテナを削除)

Dockerfileがある場合のイメージビルド
docker build ./  -t 【タグ(イメージ名前)】
コンテナの作成と起動
docker run -it -d —name 【コンテナ名指定】【上記で指定したタグ(イメージ名前)】 (-p ホスト:コンテナ)
コンテナの中に入る
docker exec -it 【上記で指定したコンテナ名指定】/bin/bash

コンテナプロセス確認
docker ps (-aで止まっているコンテナも表示)
コンテナ停止と起動
docker stop/start 【コンテナ名】
コンテナイメージの削除
docker rm 【コンテナ名】
docker rmi 【イメージ名】

全コンテナ停止
docker stop $(docker ps -q)
全コンテナ削除
docker rm $(docker ps -q -a)
全イメージ削除
docker rmi $(docker images -q)
容量の確認と不要なコンテナイメージ削除
docker system df
docker system prune

<Dockerfile書き方>
RUN→イメージ作成時
COPY→ディレクトリのコピー
CMD→コンテナ起動時
WORKDIR→コンテナ内でcdコマンド

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

vuejsでsuggestフォームを作る2

前回はvue.jsをDockerで動かすところまで進めました
第2回目はDockerにElasticsearchの環境をつくるところまでです

やりたいことのおさらい

キーワードを入力していくとキーワード候補エリア、関連エリア(もしかして、、、これ?みたいな)が表示されるやつを作ります

Elasticsearch環境作成

1.Dockerfileを作成

Install Elasticsearch with Docker を参考にDockerfileを作ります

ディレクトリ構成はこんな感じにします

$ tree
.
├── Makefile
├── docker-compose.yml
├── dockerfile
│   ├── elasticsearch
│   │   └── Dockerfile
│   └── vue
│       └── Dockerfile
dockerfile/elasticsearch/Dockerfile
FROM docker.elastic.co/elasticsearch/elasticsearch:6.7.2
RUN elasticsearch-plugin install analysis-kuromoji

日本語を扱いたいのでkuromojiをinstallしています

2.docker-compose.yamlを作成

docker-compose.yaml
version: '3'
services:
  vue_app:
    build: dockerfile/vue
    ports:
      - 9000:9000
    volumes:
      - .:/app
    stdin_open: true
    tty: true
    command: /bin/sh

  elasticsearch:
    build: dockerfile/elasticsearch
    ports:
      - "9200:9200"
    volumes:
      - esdata1:/usr/share/elasticsearch/data
    networks:
      - esnet
volumes:
  esdata1:
    driver: local

networks:
  esnet:

コンテナ削除後でもデータを利用できるように
データボリュームを作成しておきます

3.起動

$ make start

4.起動確認

$ curl -XGET localhost:9200
{
  "name" : "xxx",
  "cluster_name" : "docker-cluster",
  "cluster_uuid" : "xxx",
  "version" : {
    "number" : "6.7.2",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "56c6e48",
    "build_date" : "2019-04-29T09:05:50.290371Z",
    "build_snapshot" : false,
    "lucene_version" : "7.7.0",
    "minimum_wire_compatibility_version" : "5.6.0",
    "minimum_index_compatibility_version" : "5.0.0"
  },
  "tagline" : "You Know, for Search"
}

次回はElasticsearchにテストデータを用意する予定です

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

docker,docker-composeでちょっと使うコマンド

dockerで今あるやつみる

docker ps -a
docker images

docker-compose buildが反映されてないっぽいとき

docker-compose build --no-cache

全部消したいとき

container全削除

-fをつけたら強制的になる

docker rm `docker ps -a -q`

image全削除

-fをつけたら強制的になる

docker rmi `docker images -q`

全部は消したくない系

例えば、今日の作業分全部いらないなってときに

dockerで今日のcontainer全部消す

-fをつけたら強制的になる

docker rm `docker ps -a | grep -E "minutes|minute|hours|hour" | awk '{print $1}'`

dockerで今日のimage全部消す

-fをつけたら強制的になる

docker rmi `docker images | grep -E "minutes|minute|hours|hour" | awk '{print $3}'`
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mongo-go-driverがうまく入らなかったので、手動でいれた。

概要

mongo-go-driverをDockerfileなり、自動で入れようとしたら上手くいかず、手動でいれるとうまくいったので、その共有。
誰かが同じ轍を踏まないように。

mongo-go-driver

Go Runtimeコンテナ

base image => golang:1.12.8 ※2019/08/15時点でのstable version

アタッチして下記を実行

パッケージ管理ツール: depのインストール

$ curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh

Projectの初期化

$ dep init ※project rootで

Mongo Driverのインストール

$ dep ensure --add go.mongodb.org/mongo-driver/mongo go.mongodb.org/mongo-driver/bson go.mongodb.org/mongo-driver/mongo/options

※goファイルでimportしていない場合、「これはまだ仮のインストールでっせ」という警告が出るかもしれませんが、
importすれば、消えるので無視でOK

あとはdocker commitなりなんなり。

参考

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

Docker上で動いているMastodonをkubernetesクラスタに移動した作業メモ

本稿のスコープ外

  • Kubernetes(以下 k8s)クラスタの構築方法
  • Mastodonの初期設定
  • kustomizeのインストール

※ 多少読み替えて頂ければ、dockerではないMastodonも移行できるはず

前提条件

  • kubectlが実行可能なこと
  • kustomize が使用可能なこと
  • 以降元dockerコンテナのファイルを持ってこれること

作業手順

実は移行自体はそれほど難しくありません。
移行が必要なのは、PostgreSQLのDBだけです。redisのデータも移行した方がよいですが、
移行しなくてもその時点のSidekiqのジョブが失われる程度で、恐らく誰も気づきません。
※ 管理者的には、Sidekiq画面のジョブ数がリセットされるのが残念かな?程度

kubernetesクラスタ上にPodを生成

Deployment定義をgithubに公開しています。
https://github.com/yakumo-saki/k8s-mastodon-deployment
私のインスタンスは、Glitch EditionなのでDockerイメージ名が違いますが、基本的に
公開しているDeploymentをそのまま使用しています。
複数インスタンス持ちなので、myinstanceをコピーして foundation としました。

foundation/kustomize.yaml は以下のように書き換えました。

namePrefix: foundation-
commonLabels:
  app: foundation

resources:
  - ../base
configMapGenerator:
  - name: config
    env: env.production
    files:  
    literals:

env.production ファイルは稼働中のインスタンスからそのまま持ってきます。
その上で、DB_HOST REDIS_HOST を変更します。

base/kustomization.yaml変更

DBを戻し終わるまでは、Web, Streaming, Sidekiqには起動して欲しくありません。
そのため、一時的に ./base/kustomization.yaml を変更します。

resources:
  - db-service.yaml
  - db-pvc.yaml
  - db-statefulset.yaml
  - web-sidekiq-pvc.yaml
  - redis-service.yaml
  - redis-pvc.yaml
  - redis-deployment.yaml
  #- web-service.yaml             ここから下をコメントアウト
  #- web-deployment.yaml
  #- streaming-service.yaml
  #- streaming-deployment.yaml
  #- sidekiq-deployment.yaml

ここまでできたら、
foundationディレクトリで
```bash
$ kustomize build | kubectl apply -f -

configmap/foundation-config-4dm8g6h6gm created
(略)
```

移行元インスタンス停止

(リハーサルなら止めなくてよい)
DBとRedisコンテナ以外 を停止します。

DB, redis バックアップ取得

# 移行元インスタンスのdockerが動いてるところで
$ docker exec mastodon_db_1 pg_dumpall > pg_dumpall.sql

# このコマンドはredisへのアクセスが停止します。(短時間ですが)
$ docker exec mastodon_redis_1 redis-cli save
OK

$ docker cp mastodon_redis_1:/data/dump.rdb .

pg_dumpall.sql dump.rdb ファイルは、kubectlを使用可能なPCにコピーしておいて下さい。

DBバックアップをk8s上のDBに戻す

$ kubectl cp pg_dumpall.sql foundation-db-0:pg_dumpall.sql

$ kubectl exec -it foundation-db-0 sh
$$ psql
root=# \i pg_dumpall.sql
(どばっと、DUMP取込のログが出る)
root=# quit

# SQLファイルを消しておかないとサーバーの容量を食うため(コンテナ再起動で消えますが)
$$ rm pg_dumpall.sql

Redisバックアップをk8s上のDBに戻す

redisのバックアップは、 /data/dump.rdb を上書きするだけです。
しかし、一つ落とし穴があります。 それは、redis稼働中にdump.rdbを上書きしても、
redisをシャットダウンする際に上書きされるという罠です。
なので、一旦redis-serverを起動しないようにします。
./base/redis-deployment.yaml を編集。

./base/redis-deployment.yaml
(略)
     containers:
        - name: redis
          image: redis:5.0-alpine
          command: ["tail", "-f", "/dev/null"]   # この行追加(またはコメントアウト解除)
          resources:
            requests:
              memory: "16M"
          livenessProbe:
          #  exec:                               # ここをコメントアウト
          #    command:
          #    - redis-cli
          #    - ping

(略)
$ kubectl get pod して、 foundation-redis-* のpod を探す。
$ kubectl cp dump.rdb foundation-redis-xxxx:dump.rdb_new

$ kubectl exec -it foundation-redis-xxxx sh

$$ ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 tail -f /dev/null    # redis-server が動いていないことを確認
   52 root      0:00 sh
   72 root      0:00 ps aux

$$ ls -lh
-rw-r--r--    1 redis    redis         92 Aug 16 05:06 dump.rdb
-rw-r--r--    1 501      dialout     5.3M Aug 16 05:28 dump.rdb_new  # 所有者が違う!

$$ cp dump.rdb_new dump.rdb
$$ chown redis.redis dump.rdb

一時的に変更した設定ファイルの復元

  • 編集した ./base/redis-deployment.yaml を元に戻します。
  • 編集した ./base/kustmization.yaml を元に戻します。

元に戻したら、いよいよ起動を行います。

kustomize build | kubectl apply -f -

動作確認

とりあえず、 curl [外向きのIP等]:3000 して何かが帰ってくればOKとします。

リバースプロクシの設定変更

動作確認ができたら、リバースプロクシの設定を変更しましょう。

積み残し

favicon

マウントしないといけないのですが、本稿では色々と事情があり割愛しています。

蛇足

.env.production じゃなくて env.productionにリネームしたのなんで?

.env.production だと ls したときに表示されないからです。 ls -a すれば見えますが。
個人的に見えないファイルが重要っていうのはあんまり好きではないので。。単純に好みですね

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

Docker環境でHeadless Chromeを使ったSystemt Specを実行する

この記事なんやねん

みなさん、Rspecで統合テスト(System Spec)書いてますか?

すっかりSeleniumを使ってヘッドレスブラウザを使った統合テストが主流になりましたが、DockerHeadless Chromeを使ったやり方が意外とヒットしなかったので投稿します。

手順

ここから早速始めていきましょう。因みに、以下の環境は揃っているという前提で進めます。

  • Dockerとdocker-composeをインストール済み
  • docker-composeを使ってRails環境を構築済み
  • Rspec実行環境を構築済み

もし、 環境構築まだやねん って状態だったら、こちらの記事を参考に開発環境を作ってみてください。

イメージの準備

Dockerfileにheadless chromeドライバをインストールしてもいいのですが、正直めんどくさいです。
brewで簡単にインストールできるのにDocker使った方が環境構築めんどくさいのも本末転倒な感じするので、Dockerhubにイメージ上がってないか検索したら案の定ありました

https://hub.docker.com/r/selenium/standalone-chrome

seleniumがイメージを提供してくれてるようですね。因みに、googleはイメージ提供していないようなので、seleniumさんのイメージをありがたく使いましょう。

サービス追加

docker-composeに以下のように既存サービスの一番下にサービスを追加してください。

docker-copompose.yml
version: '3'
services:
  ### 省略 ###
  chrome:
    image: selenium/standalone-chrome
    ports:
      - "4444:4444"

これだけです。やっぱりイメージ使えば楽ですなあ。

Rspecの設定

次に、Rspecの設定をしていきます。Capybaraとrails_helperを設定するだけで済むので簡単です。

Gemfileの修正

もしまだライブラリをインストールしていなければ以下を追記してから、bundle installしてください。

Gemfile
group :test do
  # Call 'byebug' anywhere in the code to stop execution and get a debugger console
  gem "database_cleaner"
  gem "factory_bot_rails"
  gem "rspec-rails"
 # 下記を追加
  gem 'capybara' 
  gem 'selenium-webdriver'
  gem 'rspec-retry'
end

Capibaraの設定

下記のファイルを作成してください。ポート番号は他でもいいですよ。

spec/support/capybara.rb
Capybara.default_driver    = :selenium_chrome
Capybara.javascript_driver = :selenium_chrome
Capybara.server_host = Socket.ip_address_list.detect(&:ipv4_private?).ip_address
Capybara.server_port = 3001
Capybara.default_max_wait_time = 5
Capybara.ignore_hidden_elements = true

Capybara.register_driver :selenium_chrome do |app|
  opts = {
    desired_capabilities: :chrome,
    browser:              :remote,
    url:                  "http://chrome:4444/wd/hub",
  }
  Capybara::Selenium::Driver.new(app, opts)
end

rails_helperの修正

下記のようにrails_helperを修正してください。
なお、spec/support/system_support.rbというSystemスペック用のヘルパーを
作成しなければ任意ってコメントしてる設定は不要です。

spec/rails_helper.rb
  ### 省略 ###

# コメントアウトされてるので、コメントアウトを外してください
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].each { |f| require f }

RSpec.configure do |config|
  ### 省略 ###

  # Systemスペック用のヘルパー。任意です。
  config.include SystemSupport, type: :system

  # 名称を一意にするために設定。任意かな。
  config.before(:all, type: :system) do
    timestamp!
  end

  # Systemスペックは不安定なのでリトライ用の設定。
  config.verbose_retry = true
  config.display_try_failure_messages = true
  config.default_retry_count = 3

  # 下記を追加
  config.before(:each, type: :system) do
    driven_by Capybara.default_driver
  end

  config.before(:each, type: :system, js: true) do
    driven_by Capybara.javascript_driver
    host! "http://#{Capybara.server_host}:#{Capybara.server_port}"
  end
end

ヘルパーの作成(任意)

Systemスペックは共通の処理(ログイン処理とか)が多いので、ヘルパーを用意しておくと便利です。
任意と書いていますが、作成しておいて損はないと思います。

spec/support/system_support.rb
module SystemSupport
  # 一意の名称を作成するために実行時のtimestampを、
  # 数字でインスタンス変数に格納するsetterです。地味に便利。
  def timestamp!(timestamp = Time.now.to_i)
    @timestamp = timestamp
  end

  # getterです
  def timestamp
    @timestamp
  end

  # ブロックの結果がtrueになるまでループするメソッド。すげえ使う。
  def wait_until(wait_time = Capybara.default_max_wait_time)
    Timeout.timeout(wait_time) do
      loop until yield
    end
  end

  # 特定のcssが登場する、もしくは、なくなるまでループするメソッド
  def wait_for_css(selector, wait_time = Capybara.default_max_wait_time, non_display: false)
    Timeout.timeout(wait_time) do
      loop until send((non_display ? :has_no_css? : :has_css?), selector)
    end
    yield if block_given?
  end

  # 非同期通信が終わるまでループするメソッド
  def wait_for_ajax(wait_time = Capybara.default_max_wait_time)
    Timeout.timeout(wait_time) do
      loop until page.evaluate_script("jQuery.active").zero?
    end
    yield if block_given?
  end
end

System Specの作成

ここまでで設定は完了してるので実際にSystem Specを書いていきましょう。
私のサンプルソースではこんな感じで書いてます。自分のソースコードに合わせて適宜書き直してください。

require "rails_helper"

# typeはsystemを設定、Javascriptも使うのでjsもtrueにしておく。
RSpec.describe "HelloWorlds", type: :system, js: true do
  # 最初にテストデータ作成
  before(:all) {
    create(:hello_world, country: "JP", hello: "こんにちわ世界", priority: 1, file_name: "jp.jpeg")
    create(:hello_world, country: "US", hello: "Hello World", priority: 2)
    create(:hello_world, country: "CN", hello: "你好 世界", priority: 3)
  }

  before(:each) {
    visit root_path

    # コンテンツが全て表示されるまで待つ
    wait_until { (page.all("div.portfolio-item").count == 3) }
  }

  context "when go to index page" do
    it "show contents" do
      expect(page).to have_css("h1", text: "Hello World")

      content = page.first("div.portfolio-item")
      expect(content.first("p.card-text").text).to eq("こんにちわ世界")
      expect(content.first("h4.card-title")).to have_link("日本")
      expect(content.first("img.card-img-top")[:src]).to match(/jp\.jpeg/)
    end
  end

  context "when go to Create page" do
    it "create content" do
      page.first("#new_hello_world").click

      # タイトル出るまで待つ
      wait_until { page.has_css?("h3", text: "New Helloworld") }

      select "ドイツ", from: "hello_world_country"
      # 一意の名称で検索してテストデータを作成
      fill_in "hello_world_hello", with: "Hallo Welt #{timestamp}"
      fill_in "hello_world_priority", with: 4

      click_on "Submit"

      # メッセージ出るまで待つ
      wait_until { page.has_content?("Hello world was successfully created.") }

      content = page.first("div.form").all("label.form-control")
      expect(content[0].text).to eq("ドイツ")
      expect(content[1].text).to eq("Hallo Welt #{timestamp}")
      expect(content[2].text).to eq("4")
    end
  end
end

テスト実行

早速テストを実行してみましょう。
下記のwebはサービス名なので適宜自分の設定してるサービス名に変更して実行してください。

% docker-compose run --rm web rspec spec/system/hello_worlds_spec.rb
Starting hr_chrome_1 ... done
Starting hr_db_1     ... done
Capybara starting Puma...
* Version 4.1.0 , codename: Fourth and One
* Min threads: 0, max threads: 4
* Listening on tcp://192.168.176.4:3001
..

Finished in 1 minute 1.58 seconds (files took 3.71 seconds to load)
2 examples, 0 failures

通りましたね、やったぜ

まとめ

Dockerでも簡単にHeadless Chromeを使った統合テスト環境を実現できました。
テストだけじゃなくてスプレイピングにも利用できるので試してみてください。

因みに、今回使ったサンプルソースはこちらのリポジトリになるので参考までにどうぞ。

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

KubeEdge環境を構築してみた by AWS EC2

TL;DR

KubeEdgeの公式手順に従ってKubeEdge環境をEC2インスタンス上に構築してみた
基本的には参考サイトに載っている手順に準じて構築している
構築までしかしていないので悪しからず!

そもそもKubeEdgeとは?

CNCF(Cloud Native Computing Foundation)の配下で最近GAされたエッジコンピューティングフレイムワークのOSS
詳しくはこちら

Kubernetes(以下、k8s)もCNCFによりGAされており、いわば本家お墨付きのようなエッジ用k8s
似たようなので、Rancherのk3sがある → GitHub

はじめに

KubeEdge環境の構築には主に3通りの方法がある(詳しくはGHのUsage参照)

  • 用意されたバイナリを実行し、k8s環境から全て自動構築する
  • k8sの環境を自前で構築し、 リリースパッケージを使って構築する
  • k8s環境を自前で構築し、ソースからビルドして構築する

今回は2つ目の方法で構築している、なぜか?
答え単純、1つ目の方法で上手くいかなかったからである!
※筆者は3回やって挫折
3度目に失敗してから「もう自分でやったほうが早いな」と思い至り2番目の方法で構築したらすんなりできた

環境

今回は構築方法までなのでエッジ側もAWSのEC2インスタンスでお手軽に構築してみた
本来ならエッジ側はラズパイなどのデバイスになるはずである
以下、環境情報

バージョン

全て最新版を使用した(2019年8月時点)

  • Docker: 19.03.1
  • Kubernetes: 1.15.2
  • kubeedge: 1.0.0

マシンスペック

マスタを大きく設定したけど、こんなにいらなかった
これならローカルのVMでも動かせそうである

  • master
    • OS: Ubuntu 18.04 LTS
    • CPU: 8
    • Memory: 32GB
    • Storage: 30GiB

稼働状況

Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests    Limits
  --------           --------    ------
  cpu                850m (21%)  100m (2%)
  memory             190Mi (1%)  390Mi (2%)
  ephemeral-storage  0 (0%)      0 (0%)
  • worker
    • OS: Ubuntu 18.04 LTS
    • CPU: 2
    • Memory: 4GB
    • Storage: 15GiB

構築手順

本題である構築手順!
EC2インスタンスの立て方については書かないので悪しからず
公式に従い「kubeadm」でk8sクラスタを構築する

共通

まずは必須であるDockerとk8s関連のパッケージをそれぞれにインストールする

sudo apt update
sudo apt upgrade -y  # アップグレードは筆者の好み、正直しない方がいいかもしれない

# docker
sudo su -
apt-get update && apt-get install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -
add-apt-repository \
  "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) \
  stable"
apt-get update && apt-get install docker-ce

cat > /etc/docker/daemon.json <<EOF
{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m"
  },
  "storage-driver": "overlay2"
}
EOF
mkdir -p /etc/systemd/system/docker.service.d
systemctl daemon-reload
systemctl restart docker
systemctl status docker

# k8s関連
apt-get update && apt-get install -y apt-transport-https curl
curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
deb https://apt.kubernetes.io/ kubernetes-xenial main
EOF
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl

echo 'KUBELET_EXTRA_ARGS=--cgroup-driver=systemd' > /etc/default/kubelet

systemctl daemon-reload
systemctl restart kubelet
systemctl status kubelet  # まだkubeletは動かないが問題ない

マスタ

共通の構築ができたらまずはマスタを構築する
今回はCNIにArmでも動く「flannel」を使う

sudo su -

# for flannel
kubeadm init --pod-network-cidr=10.244.0.0/16
sysctl -w net.bridge.bridge-nf-call-iptables=1

# KubeEdge用に編集する
vim /etc/kubernetes/manifests/kube-apiserver.yaml
- - --insecure-port=0
+ - --insecure-port=8080
+ - --insecure-bind-address=0.0.0.0

# flannelのPodをデプロイする
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
kubectl label nodes ip-172-31-38-225 type=master
kubectl get nodes -L type  # StatusがReadyになっているはず

# KubeEdgeで使うCRDをデプロイする
wget -L https://github.com/kubeedge/kubeedge/blob/master/build/crds/devices/devices_v1alpha1_devicemodel.yaml
kubectl create -f devices_v1alpha1_devicemodel.yaml
wget -L https://github.com/kubeedge/kubeedge/blob/master/build/crds/devices/devices_v1alpha1_device.yaml
kubectl create -f devices_v1alpha1_device.yaml

# KubeEdgeのリリースパッケージをダウンロード
curl -LO https://github.com/kubeedge/kubeedge/releases/download/v1.0.0/kubeedge-v1.0.0-linux-amd64.tar.gz
tar -xf kubeedge-v1.0.0-linux-amd64.tar.gz -C /etc

# KubeEdgeで使う証明書を発行
wget -L https://github.com/kubeedge/kubeedge/blob/master/build/tools/certgen.sh
chmod +x certgen.sh
bash -x ./certgen.sh genCertAndKey edge

# マスタ側のKubeEdgeを起動する
cd /etc/kubeedge/cloud
nohup ./edgecontroller > edgecontroller.log 2>&1 &
tail edgecontroller.log

# エッジ側に同じものを送るためにコピーしておく
cp -r /etc/kubeedge /home/ubuntu
chown ubuntu:ubuntu /home/ubuntu/kubeedge -R

ローカル

今回はマスタ側で発行した証明書などをエッジ側にも置くためにローカルからSCPでコピーして送った
※マスタとエッジが直接SSHできるならこれはいらない

scp -r -i ~/.ssh/aws/admin.pem ubuntu@<master-ip>:/home/ubuntu/kubeedge ~/
scp -r -i ~/.ssh/aws/admin.pem /home/ubuntu/kubeedge ubuntu@<edge-ip>:/home/ubuntu/

エッジ

さて、マスタが構築できたらエッジを構築したk8sクラスタに参加させる
※「kubectl」をマスタ側で叩いているが、エッジでも叩けるようにすればどちらでも問題ない
詳しくはこちら

sudo su -

# 送っておいたファイルを適切な場所に置いておく
cp -r /home/ubuntu/kubeedge /etc
chown root:root /etc/kubeedge -R

# 構築したクラスタに参加させる ※適宜置き換える
kubeadm join 172.31.38.225:6443 --token <token> \
    --discovery-token-ca-cert-hash sha256:<ca-cert-hash>

# in master
# nodeの名前は適宜置き換える
kubectl get nodes  # 新しくnodeが追加されているはず
kubectl lobel nodes ip-172-31-39-8 type=worker
wget -L https://github.com/kubeedge/kubeedge/blob/release-1.0/build/node.json
vim node.json
- name: "fb4ebb70-2783-42b8-b3ef-63e2fd6d242e",
+ name: "ip-172-31-39-8",
kubectl apply -f node.json

# エッジ側のKubeEdgeを起動する
cd /etc/kubeedge/edge
vim conf/edge.yaml  # nodeの名前を適切に修正する
- url: wss://172.31.38.225:10000/e632aba927ea4ac2b575ec1603d56f10/fb4ebb70-2783-42b8-b3ef-63e2fd6d242e/events
+ url: wss://172.31.38.225:10000/e632aba927ea4ac2b575ec1603d56f10/ip-172-31-39-8/events
- node-id: fb4ebb70-2783-42b8-b3ef-63e2fd6d242e
+ node-id: ip-172-31-39-8
- hostname-override: fb4ebb70-2783-42b8-b3ef-63e2fd6d242e
+ hostname-override: ip-172-31-39-8
nohup ./edge_core > edge_core.log 2>&1 &
tail edge_core.log

テスト

最後にちゃんと動いてるか、簡単に確認する

kubectl apply -f https://raw.githubusercontent.com/kubeedge/kubeedge/release-1.0/build/deployment.yaml
kubectl get pods
kubectl get deploy

おわりに

とりあえず動くものを構築してみた
最初の方にも書いたが、参考サイトに従って構築したので不明な点は参考サイトを参照してほしい
正直KubeEdge自体が正しく動いているか、確信が持ててない...
故に次は、KubeEdgeのExampleを試してみたいと思う
https://github.com/kubeedge/examples/tree/master/led-raspberrypi

参考

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

Docker compose 備忘録

Docker とか詳しく分かってない人が Swagger UI を docker で立ち上げるまでの docker-compose 備忘録

Docker インストール

  1. Docker Hub の アカウント作成 & Docker desktop for mac をダウンロード

    Docker Hub

  2. Docker desktop をインストール & インストールチェック

$ docker version
Client: Docker Engine - Community
 Version:           19.03.1
 API version:       1.40
 Go version:        go1.12.5
 Git commit:        74b1e89
 Built:             Thu Jul 25 21:18:17 2019
 OS/Arch:           darwin/amd64
 Experimental:      false

Server: Docker Engine - Community
 Engine:
  Version:          19.03.1
  API version:      1.40 (minimum version 1.12)
  Go version:       go1.12.5
  Git commit:       74b1e89
  Built:            Thu Jul 25 21:17:52 2019
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          v1.2.6
  GitCommit:        894b81a4b802e4eb2a91d1ce216b8817763c29fb
 runc:
  Version:          1.0.0-rc8
  GitCommit:        425e105d5a03fabd737a126ad93d62a9eeede87f
 docker-init:
  Version:          0.18.0
  GitCommit:        fec3683

launch

  1. swagger.yaml を作成
  2. docker-compose.yml を作成
  3. docker-compose.yml がある階層で docker-compose up -d
  4. http://localhost:3200 にアクセス
  5. Swagger UI がブラウザで見れる!!!!

今回作成した階層構造はこうなりました。

swagger/
  ├ swagger.yaml
  └ docker/
      └ docker-compose.yml

コンテナの起動

  • docker-compose up
    • コマンドを実行した階層の docker-compose.yml をもとにコンテナ起動

exit した場合はコンテナが終了するので docker-compose up -d でデタッチモード (バックグラウンド) で起動すると動き続ける。

  • docker-compose start
    • 停止中のコンテナ起動

コンテナの確認

  • docker-compose ps or docker container ls
$ docker-compose ps
CONTAINER ID        IMAGE                   COMMAND                  CREATED             STATUS              PORTS                            NAMES
c50f725ebe68        swaggerapi/swagger-ui   "sh /usr/share/nginx…"   25 hours ago        Up 9 hours          80/tcp, 0.0.0.0:3200->8080/tcp   docker_swagger_1

コンテナの停止・削除

  • docker-compose stop

    • コンテナ停止
  • docker-compose down

    • docker-compose で作成されたコンテナ関連削除
    • docker-compose down -v で volume ごと削除

Example

  • 今回使用した docker-compose.yml の例
docker-compose.yml
version: '3'

services:
  swagger:
    image: swaggerapi/swagger-ui
    environment:
      API_URL: /swagger.yaml
      BASE_URL: /
    volumes:
      - ../swagger.yaml:/usr/share/nginx/html/swagger.yaml
    restart: no
    ports:
      - 3200:8080

version

  • 起動する docker の version
    • 3 系が最新

services

  • Docker でアプリケーションを動かすための各要素
    • ls で確認したときの name になる
    • docker_swagger_1

image

  • 起動するコンテナのイメージ
    • ローカルになければリモートから pull ってくる

environment

  • 環境変数
    • image 内の環境変数を設定

volumes

  • マウントする設定ファイル/ディレクトリの指定
    • ホスト内のファイル/ディレクトリ : マウントするファイル/ディレクトリのコンテナ内のパス
volumes:
  - ./dir:/var/www/dir

ホスト上の dir 内でファイルを作るとコンテナの dir 内にも作られる。
(docker-compose.yml から見た相対パス)

一番上の例だと ../swagger.yaml を更新するとコンテナ内の /usr/share/nginx/html/swagger.yaml も更新される。

restart

  • 実行時に再起動するかどうか
option 挙動
no 再起動しない : デフォルト
always 再起動する
on-failure ?
unless-stopped ?

今回は特に気にしてなかったので詳しく調べていません

ports

  • コンテナの port 指定

参考

DockerをMacにインストールする(更新: 2019/7/13) - Qiita

Swagger 3.0のOAuth認証にCognito User PoolsのOAuth Clientを使う | DevelopersIO

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

AngularのためのDockerfileの書き方

皆さん、Angular使ってますか?

私は仕事でも趣味でもAngularを使うAngular大好き人間ですが、共同開発なんかで動作確認してもらうとき、node入れて@angular/cli入れてng serveして…となってしまい、結構環境が汚れてしまうのが難点だなーと思っていました。

あと、本番環境のときng build --prodして出来たビルドファイルをWinSCPで直上げするのも何とかしたい。いちいちコマンド叩いてWinSCP開くのめんどくせぇ…

でも@angular/cliの便利さは捨てがたい…特にオートリロードが…

というわけで、「AngularアプリをDockerの上で動かす」ことに挑戦してみましたので、知見を残しておきます。

仕様

  • 本番環境としても開発環境としても使えること
  • 本番環境ではnginxを用いること
  • 開発環境ではオートリロードしたい
  • 一つのDockerfileにまとめる

方法

Dockerのマルチステージビルドを使います。

Dockerfile
### ベースステージ ###
FROM node:lts-alpine as base

# @angular/cliをグローバルインストール
RUN npm install -g @angular/cli

# ワーキングディレクトリの設定
WORKDIR /some-angular-app

# package.jsonをコピー
COPY ./package*.json /some-angular-app/

# 一度node_modulesを削除してからnpm install
RUN rm -rf node_modules && npm install


### ビルドステージ ###
FROM base as build

# 全てのソースファイルをコピー
COPY ./ /some-angular-app/

# 本番用ビルド
RUN ng build --prod --output-path=./dist/build-by-docker


### プロダクションステージ ###
FROM nginx:alpine as prod

# ビルドステージで生成されたファイルをnginxの公開用ディレクトリにコピー
COPY --from=build /some-angular-app/dist/build-by-docker /usr/share/nginx/html

# nginx.confをコピー
COPY ./nginx/default.conf /etc/nginx/conf.d/default.conf

Dockerfileではソースのコピーとビルド処理、ビルド結果のコピーのみを行い、
主たるコマンドはdocker-compose.ymlに書きます。

docker-compose.yml
version: "3.4"

services:
    node:
        build:
            context: ./
            dockerfile: "Dockerfile"
            target: base
        ports:
            - "4200:4200"
        command: sh -c "ng serve --host 0.0.0.0 --poll=1000"
        volumes:
            - .:/some-angular-app
            - node_modules:/some-angular-app/node_modules
        tty: true
volumes:
    node_modules:
        driver: "local"

こちらは開発環境用のdocker-compose.ymlです。Dockerfileにおけるbaseステージを継承して動きます。

volumesでソースコードをマウントしているため、Dockerfile内で特にCOPYをしなくてもこれで動きます。
node_modulesをマウント対象から除外するのを忘れないようにして下さい。

commandですが、ng serve --host 0.0.0.0 --poll=1000がミソです。

Dockerコンテナは、ホストPCとlocalhostを共有していますが、コンテナ内で動いている@angular/cliにはそんなことは知ったこっちゃないため、@angular/cliが言うところの「localhost」は、Dockerコンテナの「localhost」だけなのです。

つまり、--hostオプションで「同ネットワークに接続しているすべてのIPアドレス」からのアクセスを許可してあげなければなりません。

また、オートリロードを正しく作動させるために、--pollオプションでファイル変更状況をポーリングするように設定しています。単位はmsです。

docker-compose.prod.yml
version: "3.4"

services:
    node:
        build:
            context: ./
            dockerfile: "Dockerfile"
            target: prod
        ports:
            - "8080:80"
        tty: true

こちらは本番用のdocker-compose.prod.ymlです。prodステージを継承して動きますが、特筆して何かをしているわけではありません。

.dockerignore
.git
dist
*Dockerfile*
*docker-compose*
node_modules

COPY時にnode_modulesがコピーされないよう、.dockerignoreに書いておきます。

default.conf
server {
    listen 80;
    server_name  localhost;

    root   /usr/share/nginx/html;
    index  index.html index.htm;
    include /etc/nginx/mime.types;

    gzip on;
    gzip_min_length 1000;
    gzip_proxied expired no-cache no-store private auth;
    gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    location / {
        try_files $uri $uri/ /index.html;
    }
}

最後に、nginxのconfフォルダにコピーしているdefault.confです。
Angularをnginxで動かすために必要な「すべてのURIに対するアクセスをindex.htmlに流す」処理を行っているだけです。適宜変更してください。

立ち上げ方

# 開発
$ docker-compose up --build

# 本番
$ docker-compose -f docker-compose.prod.yml up --build 

まとめ

マルチステージビルドを使って、docker-composeコマンド一発でAngularアプリを立ち上げる環境を構築することができましたが、予想通りというかなんというか、直接ホストPCでng serveしたときよりはかなりビルドが重いです。

他人の環境を汚さないようにするだけでもかなりメリットはありますが、自分がゴリゴリ開発する立場だったら素直にホストPCに@angular/cli入れた方がいいと思いました。

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

WebスクレイピングしたデータをGrafana で可視化する ②構築編

前回の記事Python(スクレイピング) + Influxdb + Grafana で作る、データの可視化について本記事で詳しく解説します。

なお、本記事は解説を目的とします。
"手順に"興味がある方はリポジトリのREADMEをご参照ください。

おさらい

前回記事の再掲。

  • できあがった構成こんな感じ Scraping_and_Grafana.png

※だいたいの構成検討とかは通勤中に(頭の中だけで)練っていたので、手を動かし始めたら1日で作れました。

各コンテナの役割

  • app: 30秒間隔でWebサイトをスクレイピングする。取得したデータを加工し、時系列DB(Influxdb)に格納する
  • influxdb: OSSの時系列データベース(time series database)
  • grafana: グラフ表示を担当

そもそも何を可視化するの?

Amazonギフト券を割安で買い付けられるサービスが存在します。
ギフト券を現金化したい売り手と、安く購入したい買い手とのマッチングサービスで、一種の市場を形成しているわけです。
この市場を、株式市場や為替市場と同じようにチャート表示してみたい というのがモチベーションです。

実際に作ったのは以下のようなチャート描画です(グラフ表示はGrafana ですが)。

キャプチャ.PNG

スクレイピング

前置き

こんな記事書いといてアレですが、スクレイピングという手法はあまり胸を張って良いものではないと考えてます(※個人の意見です)。
数十秒~数分間隔のスクレイピングならまだしも、ミリ秒レベルの間隔でなんか実行してしまうと「それDoSじゃん」と思ってしまいます。

したがって、本記事および成果物(Github)では 私が実際に実装したスクレイピングツールの宛先URLおよびサービスは晒しませんのでご理解お願いします。

スクレイピング実装

Pythonのurllib3BeautifulSoup4を利用。これらを利用したスクレイピング自体については詳しく解説しませんのでググってください。
ここでは、本ケースで取得したいデータの前提を記載します。

まず、Webページ内に以下のような表(テーブル)があるとします。
図1.png

そして、このページ要素から以下のデータを集めることを考えます

  • 最も安い販売レート(最良レート): best_rate
  • レートの平均値: avg
  • ワーストレート: worst_rate
  • 総枚数: amount_sum

さて、htmlのテーブル要素の場合大抵以下のような構成になっていると思います。
図4.png

ここでポイントとなるのは、HTMLにはタグとその階層構造があるということです。
表の行要素(黄色の部分)とセル要素(緑の部分)がそれぞれtrタグとtdタグにあたり、セル要素をforループで取得していきます。
今回取得したいのはギフト券のレートと枚数なので、for ループで全ての要素をした後 必要なデータを抜き出していく流れです。
以下がコードの抜粋です。

app/main.py(抜粋)
import urllib3
import certifi
from bs4 import BeautifulSoup

def scrape(url):
    # HTTPリクエストを生成
    http = urllib3.PoolManager(
        cert_reqs='CERT_REQUIRED',
        ca_certs=certifi.where()
    )

    # 対象URLをHTTP GETして保持
    res = http.request('GET', url)
    soup = BeautifulSoup(res.data, 'html.parser')
    table_body = soup.select_one('#tbody1')

    amounts = []
    rates = []
    for tr in table_body.find_all('tr'):
        temp_list = []
        for td in tr.find_all('td'):
            temp_list.append(td.string)
        amounts.append(int(temp_list[0].replace('枚', ''))) # "チケット枚数"リストに追加
        rates.append(float(temp_list[3].replace('%', ''))) # "レート"リストに追加

    # 最良レート、ワーストレート
    best_rate = min(rates) ; worst_rate = max(rates)

    # レート平均(重みつき)
    avg = ...

    return {'best_rate': best_rate, 'worst_rate': worst_rate, 'avg': avg, 'amount_sum': sum(amounts)} # チケット枚数の総量: sum(amounts)

図5.png

「全ての要素をした後 必要なデータを抜き出して」いるのが以下の部分です。

        amounts.append(int(temp_list[0].replace('枚', ''))) # "チケット枚数"リストに追加
        rates.append(float(temp_list[3].replace('%', ''))) # "レート"リストに追加

取得データの書き込み

ここまででスクレイピングの実行およびデータ加工が完了しました。
続いて取得データをInfluxdbに書き込みすれば良いのですが、Pythonのライブラリが準備されています。
したがって何も難しいことなく実装できます。

なお、ここで書き込みを行う際にJSON形式のリクエストボディを生成するため、上述のスクレイピングの関数では辞書型オブジェクトを返すようにしています。

return {'best_rate': best_rate, 'worst_rate': worst_rate, 'avg': avg, 'amount_sum': sum(amounts)} # チケット枚数の総量: sum(amounts)
app/main.py(抜粋)
impoert os
from influxdb import InfluxDBClient

# influxdbへ書き込み処理を行う
def insert(measurement, values):
    client = InfluxDBClient(
        host=os.environ['INFLUXDB_HOST'],
        port=os.environ['INFLUXDB_PORT'],
        database=os.environ['INFLUXDB_DATABASE']
    )

    json_payload = [
    {
        "measurement": measurement,
        "fields": values
    }
    ]
    client.write_points(json_payload)

書き込み先DBの情報は環境変数から取得します。
pythonが実行されるDocker コンテナの起動時に環境変数を定義するようDockerfile に記述します。

app/Dockerfile
FROM docker.io/python:3.7.4-alpine3.10

RUN apk add --no-cache bash && \
    pip3 --no-cache-dir install influxdb urllib3 beautifulsoup4 certifi

COPY ./main.py /app/main.py

ENV INFLUXDB_HOST="scraping_and_grafana_influxdb_1" \
    INFLUXDB_PORT="8086" \
    INFLUXDB_DATABASE="mydb"

コンテナ名や起動ポート番号を変更する場合は適宜変更してください。あとデータベース名("mydb")も同様。

Influxdbのススメ

Influxdbは時系列データベースです。
簡単なクエリ実行を例に動きを見てみましょう。

コンテナデータベース起動~データベース作成まで。

$ docker images
REPOSITORY                  TAG                 IMAGE ID            CREATED             SIZE
docker.io/influxdb          1.7                 d1e103e42e17        4 weeks ago         258 MB
$ docker run -d --rm --name influxdb -p 8086:8086 docker.io/influxdb:1.7
7a554***
$ docker exec -it influxdb influx -precision rfc3339
Connected to http://localhost:8086 version 1.7.7
InfluxDB shell version: 1.7.7
> show databases
name: databases
name
----
_internal
> CREATE DATABASE sample_db
> SHOW DATABASES
name: databases
name
----
_internal
sample_db
> USE sample_db
Using database sample_db

テーブル作成~レコード挿入

ここで、Influxdbの用法について。
Influxdb では一般のRDBMSでいう所のTABLEをMEASUREMENTと呼称します。正にメトリクスと位置付けているんですね。

そして、挿入クエリの書式は以下。

キャプチャ2.PNG
(引用元: Influxdb and time series data - Slideshare)

面白いのは、レコードにおいてタグを分けています(「値」はVALUEと言うべきか、measurement と言うべきか...)。
タグは省略可能です。

テーブル名とタグはカンマ区切り、VALUEはスペースの後記述します。
また、InfluxdbではCREATE TABLE文も省略可能です(正しくはCREATE MEASUREMENT ですが)

> SHOW MEASUREMENTS
>
> INSERT cpu,host=A,region=tokyo usage=0.6,LA=0.3
>
> SHOW MEASUREMENTS # "CREATE MEASUREMENT"文は不要
name: measurements
name
----
cpu
> SELECT * FROM cpu
name: cpu
time                           LA  host region usage
----                           --  ---- ------ -----
2019-08-13T16:05:25.964591986Z 0.3 A    tokyo  0.6

INSERT文で指定したVALUEはusage=0.6,LA=0.3のみでしたが、自動的にタイムスタンプが付与されているのが分かります。
Influxdbでは、基本的にこのtimeカラムをがキーの1つとなるように設計するとGoodだと思います。

データが取得出来たらGrafanaで可視化

割愛します。
プラグインを選択して進んでいくだけです。

まとめ

とりあえず今回は自宅の仮想サーバで実現しました。
今後の展望としては、お勉強として以下のことにも少しずつ挑戦できたら楽しいなと思ってます。

  • 自動テストを組み込んでみる
  • DBに書き込みしたデータの信頼性担保(EFSを利用?)
  • コンテナオーケストレーションツールを組み合わせてみる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

これからIBM Cloud CLIをインストールする方へ

皆さん、こんにちは。戸倉彩です。

今回は、IBM Cloud をコマンドラインから操作することができるCLIのインストールについて解説いたします。イベントやセミナー等で目的に応じてIBM Cloud CLIのインストールで案内される方法はさまざまですが、こちらも合わせて参考にしていただけばと思います。

IBM Cloud CLI とは

IBM Cloud CLIは、IBM Cloudのリソースを管理するためコマンド・ライン・インターフェース(CLI)のことです。現時点では、Mac OX、Windows、Linuxに対応したIBM Cloud CLIが公式サイトから提供されています。

IBM Cloud Kubernetes Servive なども利用する場合は Developer Tools もまとめて1発インストールしておくのが便利!

IBM Cloudで提供されているサービスによって、IBM Cloud CLIに加えてIBM Cloud Developer Toolsと呼ばれている開発者ツールのプラグイン等をインストールする必要があります。後から追加する手間を考えると、初めから一度に最新のIBM Cloud CLIを含む一連のIBM Cloud 開発ツールをインストールしておくと役立ちます。

ここで紹介するコマンドを実行してインストールを行うと、最新のIBM Cloud CLIと下記のツールを一度に入手することができます。GitやDocker、Kubernetesなどを利用する場合に必要なコマンドも一緒にインストールされるので時間の短縮にもなります。
・ Homebrew (Mac のみ)
・ Git
・ Docker
・ Helm
・ kubectl
・ curl
・ IBM Cloud Developer Tools プラグイン
・ IBM Cloud Functions プラグイン
・ IBM Cloud Object Storage プラグイン
・ IBM Cloud Container Registry プラグイン
・ IBM Cloud Kubernetes Service プラグイン

インストールする前の準備

  • IBM Cloud アカウント の取得する
  • 次のシステム要件を満たしていることを確認する
    • DockerはStable チャネル (安定版) を使用する必要があるため、バージョン 1.13.1 以上を利用する
    • 一部の機能はWindows 10 Pro を実行していないとサポートされません

インストール方法

下記のコマンドを実行します。
・MacおよびLinux環境の場合

curl -sL https://ibm.biz/idt-installer | bash

・Windows環境の場合
Windows 10 Pro の場合、管理者としてPowerShellで次のコマンをを実行します。

[Net.ServicePointManager]::SecurityProtocol = "Tls12"; iex(New-Object Net.WebClient).DownloadString('https://ibm.biz/idt-win-installer')

参考リソース: IBM Cloud CLI および Developer Tools の概説

シンプルに IBM Cloud CLI のみをインストールしたい場合

必要最低限のIBM Cloud CLIをインストールしたい、もしくは上記の手順でインストールが上手く実行できなかった時は、公式サイトから直接インストーラーをダウンロードして、ローカルで実行するという方法があります。

32ビットのバージョンの環境をお使いの場合は、GitHubのIBM-Cloudリポジトリから直接ダウンロード入手することが可能です。

参考リソース: スタンドアロン IBM Cloud CLI のインストール

IBM Cloud CLI の始め方

IBM Cloud CLIが正常にインストールされたことを検証するには、helpコマンドを実行します。

ibmcloud help

IBM Cloudを実際に操作するためには、利用するIBM Cloud環境にloginコマンドでログインを行います。

ibmcloud login

企業などでシングルサインオンを利用している場合には--ssoのオプションを追加してログインをしてください。

ibmcloud login --sso

IBM CloudにIBM Cloud CLIでログインは無事にできましたでしょうか?
あとは、IBM Cloudのサイトやhelpを参考にしながら、操作を行ってみてください。

今回は以上です。お疲れ様でした!

Have a nice Geek Life♪  
※Twitterで最新情報配信中 @ayatokura

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

Docker-composeで立ち上げてVS CodeでJupyterを使って、OpenCVで画像処理する開発環境を作った件

目的

Pythonで画像処理がしたくて開発環境を作ろうと思った次第です。
単体でやってる方は多かったけど、同時にやってる方がいなくて意外と時間かかったので自分用のメモ。
composeにしたのは毎回コマンド打つのが面倒だった。 ← 知識の浅さを露呈?
ついでにVS CodeでJupyterを使う。

一応Githubに置いておきます。

Dockerfileとdocker-compose.yml

Docker-composeでJupyterLabを簡単構築を参考に(まるマパクり)させてもらってdocker-compose.ymlを追加する。
そのままだとJupyterのimageしか使えないのでDockerfileに移す。

docker-compose.yml
version: '3'
services:
  jupyterlab:
    # これだとJupyterのimageしか使えないのでDockerfileに移す
    # image: jupyter/datascience-notebook:latest
    build: .
    user: root
    environment:
      NB_UID: 1000
      NB_GID: 100
      GRANT_SUDO: "yes"
    volumes:
       - "./work:/home/jovyan"
    privileged: true
    ports:
      - "8888:8888"
    restart: unless-stopped
    command: start.sh jupyter lab --NotebookApp.token=''

Dockerhubからjjanzic/docker-python3-opencvのimageを使う。
jupyter-dockerから使いたいimageをもってくる。

Dockerfile
FROM python:3.7

ARG project_dir=/home/jovyan
WORKDIR $project_dir

FROM jjanzic/docker-python3-opencv

# https://jupyter-docker-stacks.readthedocs.io/en/latest/using/selecting.html
FROM jupyter/scipy-notebook:latest

COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt

あとはdocker-compose build & upでいける。
ちなみに、下記にしたかったがこれだとうまく立ち上がらない。。。
誰か理由を教えてください。。。

Dockerfile(NG)
FROM python:3.7
FROM jupyter/scipy-notebook:latest

ARG project_dir=/home/jovyan
WORKDIR $project_dir

# 他に必要なものを入れる
FROM jjanzic/docker-python3-opencv
RUN pip install opencv-python

JupyterをVS Codeで使う

Jupyterをブラウザで使う分にはこのままで問題ないが、せっかくならVS CodeでJupyterを使いたい。
VS Codeはローカルを参照しているので、拡張機能Remote - Containersを使ってDockerに入る。
使い方はこちらの方が分かり易くまとめてくれてるので参考にしてDockerを立ち上げる。

VSCodeの左下角をクリックして、Remote-Containers: Open Folder in Containerを選択して、docker-compose.ymlのあるプロジェクトのルートディレクトリを選択しすると立ち上がる
スクリーンショット 2019-08-16 10.52.40.png

普段使っている拡張機能が反映されないので.devcontainer.jsonのextensionsに追加する。

.devcontainer.json
// See https://aka.ms/vscode-remote/devcontainer.json for format details or
// https://aka.ms/vscode-dev-containers/definitions for sample configurations.
{
    "dockerComposeFile": "docker-compose.yml",
    "service": "jupyterlab",
    "workspaceFolder": "/",
    "extensions": [
        "ms-python.python",
        "donjayamanne.jupyter",
        "formulahendry.auto-close-tag",
        "formulahendry.auto-rename-tag",
        "coenraads.bracket-pair-colorizer",
        "dbaeumer.vscode-eslint",
        "msjsdiag.debugger-for-chrome",
        "ms-azuretools.vscode-docker",
        "donjayamanne.githistory",
        "huizhou.githd",
        "eamodio.gitlens",
        "oderwat.indent-rainbow",
        "ms-ceintl.vscode-language-pack-ja",
        "ibm.output-colorizer",
        "ryu1kn.partial-diff",
        "ionutvmi.path-autocomplete",
        "esbenp.prettier-vscode",
        "wallabyjs.quokka-vscode",
        "mechatroner.rainbow-csv",
        "ms-vscode-remote.remote-containers",
        "humao.rest-client",
        "shan.code-settings-sync",
        "shardulm94.trailing-spaces",
        "octref.vetur",
        "vscode-icons-team.vscode-icons",
        "wakatime.vscode-wakatime"
    ]
}

ちなみに、ここに書くのは拡張機能の名前の右側にあるやつを追加する。
一発でJsonに吐き出す方法が分からなかったので地道に追加した。
スクリーンショット 2019-08-14 16.14.57.png

JupyterをVS Codeで使えた

ちなみに、Jupyterを入れると、"#%%"の上に"Run Cell"が出てくるのでクリックすると実行画面が表示される。
スクリーンショット 2019-08-16 10.15.16.png

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

docker-composeでMongoDBのReplicaSetを構築する

背景

ScalaでMongoDBへMulti-document transaction接続するの続き. MongoDBへMulti-document transaction接続するためにはReplicaSetが必要になる. ReplicaSetを構築してみたけどMongoDBよく分からん.,, 試行錯誤して技術的に解決できない部分も含めてある程度腑に落ちたので整理してみた. Transactions

環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.14.5
BuildVersion:   18F132

$ docker --version
Docker version 19.03.1, build 74b1e89

$ docker-compose --version
docker-compose version 1.24.1, build 4667896b

ReplicaSet構成

最小構成のPrimary, Secondary, Arbiterとする. Replication

ReplicaSetのコンテナを定義する

Primary, Secondary, Arbiterをdocker-composeにて定義する. ディレクトリ構成, 各パラメータの意図は下記の通り.

$ tree
.
├── docker-compose.yml
└── volumes
    └── mongodb
        ├── docker-entrypoint-initdb.d
        ├── etc
        │   └── mongod-keyfile # Permission: 600
        └── root
            ├── 000_init_replicaSet.js
            ├── 001_init_database.js
            └── 002_init_user.js
  • Primaryコンテナ
    • 初期設定用のrootアカウントを定義する
    • 初期化スクリプトをVolume共有する
  • 各コンテナ
    • Multi-document transactionを使用したいのでMongoDBのVersionは4.xを指定する
    • HostOSのPort用に各MongoDBコンテナのPortを切り分ける
    • 認証鍵をVolume共有する
docker-compose.yml
version: '3'
services:

  # https://hub.docker.com/_/mongo
  mongodb-primary:
    image: mongo:4.2.0-bionic
    container_name: mongodb-primary
    hostname: mongodb-primary
    command: >
      mongod
      --port 27017
      --replSet replset
      --auth --keyFile /etc/mongod-keyfile
    environment:
      MONGO_INITDB_ROOT_USERNAME: root
      MONGO_INITDB_ROOT_PASSWORD: password
    volumes:
      - ./volumes/mongodb/root:/root:ro # Initialize script
      - ./volumes/mongodb/etc/mongod-keyfile:/etc/mongod-keyfile:ro # Permission: 600
    ports:
      - 27017:27017
    networks:
      - replset
    depends_on:
      - mongodb-secondary
      - mongodb-arbiter
    restart: on-failure

  # https://hub.docker.com/_/mongo
  mongodb-secondary:
    image: mongo:4.2.0-bionic
    container_name: mongodb-secondary
    hostname: mongodb-secondary
    command: >
      mongod
      --port 27018
      --replSet replset
      --auth --keyFile /etc/mongod-keyfile
    volumes:
      - ./volumes/mongodb/etc/mongod-keyfile:/etc/mongod-keyfile:ro # Permission: 600
    ports:
      - 27018:27018
    networks:
      - replset
    depends_on:
      - mongodb-arbiter
    restart: on-failure

  # https://hub.docker.com/_/mongo
  mongodb-arbiter:
    image: mongo:4.2.0-bionic
    container_name: mongodb-arbiter
    hostname: mongodb-arbiter
    command: >
      mongod
      --port 27019
      --replSet replset
      --auth --keyFile /etc/mongod-keyfile
    volumes:
      - ./volumes/mongodb/etc/mongod-keyfile:/etc/mongod-keyfile:ro # Permission: 600
    ports:
      - 27019:27019
    networks:
      - replset
    restart: on-failure

networks:
  replset:
    ipam:
      config:
      - subnet: 192.168.1.0/24 # Any

初期化スクリプトを作成する

下記の技術的課題により各コンテナ起動後に初期化スクリプトにてReplicaSetを構築するアプローチを取る.

ReplicaSet初期化スクリプト

000_init_replSet.js
rs.initiate(    {   _id : "replset"
                ,   members:    [   {   _id: 0,   host: "mongodb-primary:27017"    }
                                ,   {   _id: 1,   host: "mongodb-secondary:27018"  }
                                ,   {   _id: 2,   host: "mongodb-arbiter:27019",   arbiterOnly: true }
                                ]
                }
);

データベース初期化スクリプト

001_init_database.js
var testdb = db.getSiblingDB('test');
testdb.createCollection('test');

JavaScript内でMongoDB shellのuse <database>が使用できないためdb.getSiblingDB(<database>)を使用する.

ユーザー初期化スクリプト

002_init_user.js
var testdb = db.getSiblingDB('test');
testdb.createUser(   {   user:   'test'
                     ,   pwd:    'password'
                     ,   roles:  [   {   role:   'root'
                                     ,   db:     'admin'
                                     }
                                 ,   {   role:   'dbOwner'
                                     ,   db:     'test'
                                     }
                                 ]
                     }
);
testdb.getUsers();

ReplicaSetの認証鍵を作成する

$ openssl rand -base64 756 > mongod-keyfile
$ chmod 600 mongod-keyfile

--auth --keyfile <path-to-keyfile>オプションを指定せずにReplicaSetを構築すると認証エラーが発生したので公式に倣って認証鍵を設定する. Update Replica Set to Keyfile Authentication

hostsを定義する

$ sudo vim /private/etc/hosts
+127.0.0.1 mongodb-primary
+127.0.0.1 mognodb-secondary
+127.0.0.1 mongodb-arbiter

HostOS側にClientがいる環境だったので名前解決のために /private/etc/hosts に追加定義する. ClientがDockerNetwork内に共存するならDockerDNSで名前解決できるので /private/etc/hosts への追加定義は必要ない. コンテナの DNS を設定 Dokcer-docs-ja

MongoDBのReplicaSetを立ち上げる

初期化スクリプト, 認証鍵をディレクトリ構成の配置に格納する.

コンテナを起動する.

$ docker-compose up -d

Primaryコンテナで初期化スクリプトを実行する.

$ docker-compose exec mongodb-primary mongo admin -u root -p password /root/000_init_replSet.js
$ docker-compose exec mongodb-primary mongo admin -u root -p password /root/001_init_database.js
$ docker-compose exec mongodb-primary mongo admin -u root -p password /root/002_init_user.js

docker-compose downコマンドでdocker-composeをもとに構築された全てを削除できる.

MongoDBのReplicaSetに接続する

MongoDB Scala Driverに下記URIを指定して接続できることを確認できた. MongoDB Scala Driver

test.infrastructure.mongodb.MongoDBConnector.scala
package test.infrastructure.mongodb

import com.typesafe.config.ConfigFactory
import org.mongodb.scala.MongoClient

object MongoDBConnector {
  private val config          = ConfigFactory.load
  private lazy val mongodbUri = config.getString("infrastructure.mongodb.uri")
  val client                  = MongoClient(mongodbUri)
  // .close
}
application.conf
infrastructure {

  mongodb {
    # username: test
    # password: password
    # database: test
    # host: mongodb-primary:27017, mongodb-secondary:27018
    # replicaSet: replset
    uri = "mongodb://test:password@mongodb-primary:27017,mongodb-secondary:27018/?authSource=test&replicaSet=replset"
  }

}
[info] No tests to run for Test / testOnly
01:40:59.028 [pool-7-thread-2-ScalaTest-running-UsecaseSpec] INFO org.mongodb.driver.cluster - Cluster created with settings {hosts=[mongodb-primary:27017, mongodb-secondary:27018], mode=MULTIPLE, requiredClusterType=REPLICA_SET, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500, requiredReplicaSetName='replset'}
01:40:59.033 [pool-7-thread-2-ScalaTest-running-UsecaseSpec] INFO org.mongodb.driver.cluster - Adding discovered server mongodb-primary:27017 to client view of cluster
01:40:59.098 [pool-7-thread-2-ScalaTest-running-UsecaseSpec] INFO org.mongodb.driver.cluster - Adding discovered server mongodb-secondary:27018 to client view of cluster
01:40:59.106 [pool-7-thread-2-ScalaTest-running-UsecaseSpec] DEBUG org.mongodb.driver.cluster - Updating cluster description to  {type=REPLICA_SET, servers=[{address=mongodb-secondary:27018, type=UNKNOWN, state=CONNECTING}, {address=mongodb-primary:27017, type=UNKNOWN, state=CONNECTING}]
01:40:59.157 [pool-7-thread-2-ScalaTest-running-TuneUsecaseSpec] INFO org.mongodb.driver.cluster - No server chosen by com.mongodb.async.client.ClientSessionHelper$1@d54dba6 from cluster description ClusterDescription{type=REPLICA_SET, connectionMode=MULTIPLE, serverDescriptions=[ServerDescription{address=mongodb-secondary:27018, type=UNKNOWN, state=CONNECTING}, ServerDescription{address=mongodb-primary:27017, type=UNKNOWN, state=CONNECTING}]}. Waiting for 30000 ms before timing out
01:40:59.219 [cluster-ClusterId{value='5d558b1b9f1f417c08b4a869', description='null'}-mongodb-secondary:27018] INFO org.mongodb.driver.connection - Opened connection [connectionId{localValue:2, serverValue:17}] to mongodb-secondary:27018
01:40:59.220 [cluster-ClusterId{value='5d558b1b9f1f417c08b4a869', description='null'}-mongodb-primary:27017] INFO org.mongodb.driver.connection - Opened connection [connectionId{localValue:1, serverValue:23}] to mongodb-primary:27017
01:40:59.276 [cluster-ClusterId{value='5d558b1b9f1f417c08b4a869', description='null'}-mongodb-secondary:27018] INFO org.mongodb.driver.cluster - Monitor thread successfully connected to server with description ServerDescription{address=mongodb-secondary:27018, type=REPLICA_SET_SECONDARY, state=CONNECTED, ok=true, version=ServerVersion{versionList=[4, 2, 0]}, minWireVersion=0, maxWireVersion=8, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=49927070, setName='replset', canonicalAddress=mongodb-secondary:27018, hosts=[mongodb-secondary:27018, mongodb-primary:27017], passives=[], arbiters=[mongodb-arbiter:27019], primary='mongodb-primary:27017', tagSet=TagSet{[]}, electionId=null, setVersion=1, lastWriteDate=Fri Aug 16 01:40:50 JST 2019, lastUpdateTimeNanos=133745025150196}
01:40:59.276 [cluster-ClusterId{value='5d558b1b9f1f417c08b4a869', description='null'}-mongodb-primary:27017] INFO org.mongodb.driver.cluster - Monitor thread successfully connected to server with description ServerDescription{address=mongodb-primary:27017, type=REPLICA_SET_PRIMARY, state=CONNECTED, ok=true, version=ServerVersion{versionList=[4, 2, 0]}, minWireVersion=0, maxWireVersion=8, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=49535877, setName='replset', canonicalAddress=mongodb-primary:27017, hosts=[mongodb-secondary:27018, mongodb-primary:27017], passives=[], arbiters=[mongodb-arbiter:27019], primary='mongodb-primary:27017', tagSet=TagSet{[]}, electionId=7fffffff0000000000000001, setVersion=1, lastWriteDate=Fri Aug 16 01:40:50 JST 2019, lastUpdateTimeNanos=133745025119925}
01:40:59.279 [cluster-ClusterId{value='5d558b1b9f1f417c08b4a869', description='null'}-mongodb-primary:27017] INFO org.mongodb.driver.cluster - Adding discovered server mongodb-arbiter:27019 to client view of cluster
01:40:59.280 [cluster-ClusterId{value='5d558b1b9f1f417c08b4a869', description='null'}-mongodb-primary:27017] INFO org.mongodb.driver.cluster - Setting max election id to 7fffffff0000000000000001 from replica set primary mongodb-primary:27017
01:40:59.281 [cluster-ClusterId{value='5d558b1b9f1f417c08b4a869', description='null'}-mongodb-primary:27017] INFO org.mongodb.driver.cluster - Setting max set version to 1 from replica set primary mongodb-primary:27017
01:40:59.281 [cluster-ClusterId{value='5d558b1b9f1f417c08b4a869', description='null'}-mongodb-primary:27017] INFO org.mongodb.driver.cluster - Discovered replica set primary mongodb-primary:27017
01:40:59.293 [cluster-ClusterId{value='5d558b1b9f1f417c08b4a869', description='null'}-mongodb-arbiter:27019] INFO org.mongodb.driver.connection - Opened connection [connectionId{localValue:3, serverValue:13}] to mongodb-arbiter:27019
01:40:59.304 [cluster-ClusterId{value='5d558b1b9f1f417c08b4a869', description='null'}-mongodb-arbiter:27019] INFO org.mongodb.driver.cluster - Monitor thread successfully connected to server with description ServerDescription{address=mongodb-arbiter:27019, type=REPLICA_SET_ARBITER, state=CONNECTED, ok=true, version=ServerVersion{versionList=[4, 2, 0]}, minWireVersion=0, maxWireVersion=8, maxDocumentSize=16777216, logicalSessionTimeoutMinutes=30, roundTripTimeNanos=11086151, setName='replset', canonicalAddress=mongodb-arbiter:27019, hosts=[mongodb-secondary:27018, mongodb-primary:27017], passives=[], arbiters=[mongodb-arbiter:27019], primary='mongodb-primary:27017', tagSet=TagSet{[]}, electionId=null, setVersion=1, lastWriteDate=Fri Aug 16 01:40:50 JST 2019, lastUpdateTimeNanos=133745058351796}
01:41:02.724 [anInnocuousThread] INFO org.mongodb.driver.connection - Opened connection [connectionId{localValue:4, serverValue:24}] to mongodb-primary:27017

所感

docker-compose up -dも含めた管理スクリプトを書けば"ポチッとな"と楽ちんになりそうだけど何かピンとこないから保留中. ちゃんちゃん.

参考文献

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

Docker環境でAtomからRubocopを使う方法

この記事なんやねん

最近はDockerを使って環境構築をするケースが多いと思いますが、Atomなどのテキストエディタのコードフォーマットで使うRubocop系のライブラリを動作させるための日本語の記事が意外となかったので投稿します。

Atomのlinter-rubocopをDocker経由で動作させるのを目的に書いていきます。
docker-composeでも動きますよ。

手順

ここから早速始めていきましょう。因みに、以下の環境は揃っているという前提で進めます。

  • Dockerとdocker-composeをインストール済み
  • Atomをインストール済み
  • docker-composeを使ってRails環境を構築済み

もし、 環境構築まだやねん って状態だったら、こちらの記事を参考に開発環境を作ってみてください。

また、自分の使ってる.rubocop.ymlこちらになります。まだ用意してなければなければ使ってみてください。

ラッパースクリプトの配置

まずは、ラッパースクリプトを作成しましょう。
配置場所はどこでもいいですが、プロジェクト配下にbin/rubocopというパスで配置するのが分かりやすいかと思います。今回の例ではそうします。

docker execしていますがdocker runコマンドでも動作します。
ただ、execの方が早いので、コンテナを起動しておいてexecコマンドにてrubocopを叩く方が使い勝手がいいかと思います。
ここら辺はお好みで変えてください。

bin/rubocop
#!/bin/bash

CMD_ARGS=""
for arg in $@
do
if [ -f "$arg" ]
then
  CMD_ARGS="$CMD_ARGS ${arg#$PWD/}"
else
  CMD_ARGS="$CMD_ARGS $arg"
fi
done

docker exec -i <コンテナ名> rubocop $CMD_ARGS

<コンテナ名>にrubocopがインストールされてるコンテナ名を記載してください。docker-compose psコマンドで確認できます。
因みに、私の環境では下記のようにhr_web_1がrailsとrubocopがインストールされてるコンテナになります。

% dc ps
   Name                  Command                State     Ports
---------------------------------------------------------------
hr_chrome_1   /opt/bin/entry_point.sh          Exit 143        
hr_db_1       docker-entrypoint.sh postgres    Exit 0          
hr_web_1      bundle exec rails s -p 300 ...   Exit 1          

記述したら、chmod +x bin/rubocopで実行権限を付与するのを忘れないでください。

スクリプトの実行準備が整ったら、docker-compose up -dでコンテナを起動しておきましょう。

プラグインの設定

linter-rubocopからInstallボタンを押して、プラグインをAtomにインストールしてください。

次に、AtomのLinter Rubocopの設定画面から、Command欄に./bin/rubocopと入力してください。

rubocop.png

これで設定は終わりです。

まとめ

全てDocker内で完結できるとスッキリしていいですね。

因みに、いくつかRailsプロジェクトを掛け持ちしてると、あるプロジェクトではDocker使ってなくてローカルでRubocop使う場合もありますよね。そんな時はわざわざAtomの設定を変更しなくても、ラッパースクリプトの最後の行のコマンドをこのように変更すれば大丈夫です。

rubocop $CMD_ARGS

はい、単純にローカルにインストールしたrubocopを実行してるだけですね。

それではDockerと過ごすRubocopライフを楽しんでください。

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

vuejsでsuggestフォームを作る1

suggestフォームパッケージを作ってみようと思います
vuejs,Elasticsearchを使っていく予定。npmパッケージ化を目指します

第1回目はDockerにVue.jsの環境をつくるところまで

やりたいこと

キーワードを入力していくとキーワード候補エリア、関連エリア(もしかして、、、これ?みたいな)が表示されるやつを作ります
#既に、こんな素敵なパッケージがあるのでそれを使えばこの話はお終いなんですけど。作りたいのでいいのです
vue-simple-suggest

ではまずにDockerで環境を作っていきます

Vue.js環境作成

1.Dockerfileを作成

Dockerfile
FROM node:12.8.0-alpine

WORKDIR /app

RUN apk update && \
    npm install -g npm vue-cli

EXPOSE 9000

CMD ["/bin/sh"]

2.docker-compose.yamlを作成

docker-compose.yaml
version: '3'
services:
  vue_app:
    build: .
    ports:
      - 9000:9000
    volumes:
      - .:/app
    command: /bin/sh

3.Makefileを作成

Makefile
PROJECT = vueSearchSuggest

.PHONY: start
start:
  docker-compose -p $(PROJECT) up -d --build

.PHONY: logs
logs:
  docker-compose -p $(PROJECT) logs -f

.PHONY: restart
restart:
  docker-compose -p $(PROJECT) kill && \
  docker-compose -p $(PROJECT) rm -f && \
  docker-compose -p $(PROJECT) up -d --build

.PHONY: kill
kill:
  docker-compose -p $(PROJECT) kill

.PHONY: ps
ps:
  docker-compose -p $(PROJECT) ps 

4.起動

$ make start

5.Vue.jsのコンテナに入る

プロセス確認
$ make ps

docker-compose -p vueSearchSuggest ps -a
           Name                        Command              State           Ports
------------------------------------------------------------------------------------------
vuesearchsuggest_vue_app_1   docker-entrypoint.sh /bin/sh   Up      0.0.0.0:9000->9000/tcp
起動中のコンテナに入る
$ docker exec -it vuesearchsuggest_vue_app_1 sh 

6.Vue.jsのインストール

/app # vue init webpack
/app # npm install
9000ポート起動に変更
config/index.js
 16 //    host: 'localhost', // can be overwritten by process.env.HOST
 17 //    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
 18     host: '0.0.0.0', // can be overwritten by process.env.HOST
 19     port: 9000, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined

6.Vue.jsのインストール

/app # npm run dev

http://localhost:9000/
を開いて動作確認

image.png
いつもの起動画面がでてきたらOK

次回はElasticsearchの環境を作っていきます

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