〇FLAGの中の人に憧れてMastodon×AWSでモンストドン作ってみた

Mastodon立ち上げたらいい会社に入れると聞いて、邪な気持ちで。。。いや、Mastodonとモンストって相性よさそうだなぁと思いたち、少し乗り遅れた感をかもしだしながら、フルにAWSを使って規模拡大しても大丈夫な構成で作ってみた。

モンストドン (https://monstdn.com)

構成

monstdn.png

最小構成のざっくり料金($1=113円、1ヶ月30日計算)

サービス 単価 月額料金
ALB 1台 × $0.0243/1H + データ転送的なの  約2000円 + α
EC2 2台(t2.nano) × $0.008/1H + データ転送的なの 約1300円 + α
RDS 1台(db.t2.micro シングルAZ) $0.028/1H + データ転送的なの 約2300円 + α
ElasticCache 1台(cache.t2.micro) $0.026/1H + データ転送的なの 約2100円 + α
S3Bucket $0.025/GB + リクエスト数的なの + α
SES $0.10/1,000通あたり + データ転送的なの + α
合計     ( 約7700円 + α なので ) ざっくり1万ぐらい

※無料枠があるので1年目はもう少しやすくできそう

やったこと

  • AWSのアカウント作成
  • IAMの作成とアカウントの初期設定(二段階認証とか、パスワードポリシーとか)
  • Route53でドメインを買う
  • SESでメール設定と制限解除申請
  • ACMの取得(無料でHTTPS通信)
  • S3バケット作成(画像とかのアップロードファイルの配信用)
  • VPCとセキュリティグループの作成
  • SES、S3へアクセスする為のIAMユーザの作成
  • ElasticCacheでRedisの作成
  • RDSでPostgreSQLの作成
  • EC2でCentOSを使ってMastodonの構築(下に詳細)とイメージ(AMI)の作成
  • AutoScallingの設定
  • ALB(ApplicationLoadBalancer)の作成(ACMをつける)
  • Route53でHostZoneのレコード設定

CentOSでのMastdon構築(20170517現在)

sudo su -
yum -y update
yum -y install vim

localectl set-locale LANG=ja_JP.utf8
localectl set-keymap jp106
localectl status

timedatectl set-timezone Asia/Tokyo
timedatectl status

dd if=/dev/zero of=/mnt/swapfile bs=1M count=2560
mkswap /mnt/swapfile
swapon /mnt/swapfile
chmod 0644 /mnt/swapfile
echo "/mnt/swapfile                             swap                    swap    defaults                0 0" >> /etc/fstab
free

vim /etc/sysconfig/selinux
 SELINUX=enforcing
 ↓
 SELINUX=disabled

systemctl disable postfix
systemctl disable auditd.service

yum -y install libxml2-devel ImageMagick libxslt-devel git curl nodejs file
yum -y install epel-release
rpm --import http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm
yum -y install ffmpeg ffmpeg-devel

yum -y group install "Development tools"
curl -sL https://rpm.nodesource.com/setup_4.x | sudo bash -

yum -y install nodejs
npm -g install yarn

yum -y install postgresql postgresql-contrib postgresql-devel
yum install -y openssl-devel readline-devel

useradd mastodon
passwd mastodon
su - mastodon
git clone https://github.com/rbenv/rbenv.git ~/.rbenv
cd ~/.rbenv && src/configure && make -C src && cd ~
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile && source ~/.bash_profile
git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build
rbenv install 2.4.1 && rbenv global $_ && rbenv rehash

# 確認
ruby -v

cd ~
git clone https://github.com/tootsuite/mastodon.git live
cd live
git checkout $(git tag | tail -n 1)

gem install bundler
bundle install --deployment --without development test
yarn install --pure-lockfile

cp .env.production.sample .env.production
sed -i "/^PAPERCLIP_SECRET=$/ s/$/`rake secret`/" .env.production
sed -i "/^SECRET_KEY_BASE=$/ s/$/`rake secret`/" .env.production
sed -i "/^OTP_SECRET=$/ s/$/`rake secret`/" .env.production

vim .env.production
#Redis,Postgresql,言語,SMTP,S3の設定

RAILS_ENV=production bundle exec rails db:setup
RAILS_ENV=production bundle exec rails assets:precompile

exit

cat << "_EOF_" > /etc/systemd/system/mastodon-web.service
[Unit]
Description=mastodon-web
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="RAILS_ENV=production"
Environment="PORT=3000"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec puma -C config/puma.rb
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_


cat << "_EOF_" > /etc/systemd/system/mastodon-sidekiq.service
[Unit]
Description=mastodon-sidekiq
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="RAILS_ENV=production"
Environment="DB_POOL=5"
ExecStart=/home/mastodon/.rbenv/shims/bundle exec sidekiq -c 5 -q default -q mailers -q pull -q push
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_

cat << "_EOF_" > /etc/systemd/system/mastodon-streaming.service
[Unit]
Description=mastodon-streaming
After=network.target

[Service]
Type=simple
User=mastodon
WorkingDirectory=/home/mastodon/live
Environment="NODE_ENV=production"
Environment="PORT=4000"
ExecStart=/usr/bin/npm run start
TimeoutSec=15
Restart=always

[Install]
WantedBy=multi-user.target
_EOF_

systemctl enable mastodon-{web,sidekiq,streaming}
systemctl start mastodon-{web,sidekiq,streaming}

cat << "_EOF_" | crontab -
RAILS_ENV=production
@daily cd /home/mastodon/live && /home/mastodon/.rbenv/shims/bundle exec rake mastodon:daily > /dev/null
_EOF_

yum -y install nginx

cat << "_EOF_" > /etc/nginx/conf.d/mastodon.conf
map $http_upgrade $connection_upgrade {
  default upgrade;
  ''      close;
}

server {
  listen 80;
  listen [::]:80;
  server_name {domainName};

  keepalive_timeout    70;
  sendfile             on;
  client_max_body_size 0;

  root /home/mastodon/live/public;

  gzip on;
  gzip_disable "msie6";
  gzip_vary on;
  gzip_proxied any;
  gzip_comp_level 6;
  gzip_buffers 16 8k;
  gzip_http_version 1.1;
  gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

  add_header Strict-Transport-Security "max-age=31536000";

  location / {
    try_files $uri @proxy;
  }

  location @proxy {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";
    proxy_pass_header Server;

    proxy_pass http://127.0.0.1:3000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  location /api/v1/streaming {
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header Proxy "";

    proxy_pass http://localhost:4000;
    proxy_buffering off;
    proxy_redirect off;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    tcp_nodelay on;
  }

  error_page 500 501 502 503 504 /500.html;
}
_EOF_

systemctl enable nginx
systemctl start nginx

# ユーザ登録後 admin設定
RAILS_ENV=production bundle exec rails mastodon:make_admin USERNAME={UserName}

メモ

EC2のDiskはSSDで(swapで使う)
ロードバランサーはApplicationの方じゃないとwebSocketがうまくいかない
コミュニティベースのシステムだからCloudFrontはあまり必要性感じなかったので使わなかった
(日本向けだしS3のバケット東京リージョンにあるし、S3もかなり性能いいし)
もしCloudFrontを使うなら、websocketできないからS3の前に置く感じ
今回CloudFrontの利点があるとすれば”ドメイン”が自分の使えることぐらいかな
CentOSじゃなくてAmazonLinux使いたかったけど、ffmpeg入れるのにやたら時間かかったからやめた。一応動いたけど(純正AWSが。。。)
DockerはDeployまで楽そうだけど、効率よくなさそうだったのでやめた
AWSでDocker使うならECSでやってみたいけど、Mastodonはすんなりできるのかなー
セキュリティ的にはロードバランサーからの80番ポートしか受け付けないように制御してるから大丈夫かな。
sshでのログインは同じVPC内に踏み台サーバ立ててと。

最後に

ここまで読んで頂きありがとうございます。
技術的なことを投稿するのはこれが初めてですが、だれかのお役にたてれたら嬉しいです。
普段はPHPとAWS少しいじる程度なのでいい勉強になりました。
ほとんど公開されている文献をもとにプラモデル感覚で作りましたので、ご指摘等あればコメント頂ければと思います。
個人でのサイト運用となりますので、落ちたらごめんなさい。

続きを読む

クラウドサーバーサービス(IaaS)の性能比較・ディスク編~IOPSの計測~

はじめに

 今回は以下のクラウドサーバーを対象にIOPSを計測し、ストレージの性能を比較します。IDCFクラウドについては初期のゾーンであるhenryと最新ゾーンのluxの2種類を計測し、ゾーン間の違いも計測してみます。

計測対象:

  • Amazon Web Services (AWS) (ボリュームタイプ:gp2/SSD)
  • Google Cloud Platform (GCP) (SSD)
  • IDCFクラウド(ゾーン:lux/SSD)
  • IDCFクラウド(ゾーン:henry/HDD)

1. 前提知識

 ディスクの性能がなぜ大事?IOPSとは?
 (自明な方は読み飛ばして下さい:bow:

1.1 仮想化環境とストレージ性能

  • 仮想化環境ではストレージの性能がネックになりやすい
  • HDDはシーケンシャルI/O、SSDはランダムI/Oが得意
  • 仮想化環境では主にランダムアクセスに対する性能が重要になる(複数の仮想マシンからのアクセスが発生するから)

参考:
 IT機器の進化とHDD性能とのギャップ
 ストレージのいろは|ソフトバンク コマース&サービス
 クラウドを加速させるSSD技術 - @IT

1.2 IOPSとは?

  • ストレージ性能を図る指標
  • ディスクが1秒当たりに処理できるI/Oアクセスの数
  • IOPSが高ければ高いほど,高性能なディスクと言える
  • IOPSはブロックサイズが小さいと大きな値になる傾向がある
  • IOPSは読み込みと書き込みの割合で値が大きく変わる

参考:
 サーバー選択の基礎 – Part4 IOPSを理解する:ITpro
 ストレージのいろは|ソフトバンク コマース&サービス

2. 計測方法

2.1 実施環境

  • 対象サーバ
AWS(EC2) GCP IDCF(HDD) IDCF(SSD)
ゾーン ap-northeast-1 asia-northeast1-a henry lux
マシンスペック m4.large(2vCPUメモリ8GB) n1-standard-2(2vCPUメモリ7.5GB) standard.M8 (2vCPUメモリ8GB) standard.M8 (2vCPUメモリ8GB)
OS CentOS 7 CentOS 7 CentOS 7 CentOS 7
ディスクサイズ 8GB 10GB 15GB 15GB

マシンスペックはAWSのm4.largeに合わせてみました。
ディスクサイズはデフォルトのままとします。

2.1 ツール

 ベンチマークツールとしてfioを使用します。

2.1.1 fioのインストール

  • # yum install epel-release
      まず、EPELのリポジトリをインストールします。

  • # yum repolist all epel*   
      追加したリポジトリが有効かどうか確認。   

  • # yum install fio
      fioをインストール。

2.1.2 計測パラメータの設定

 小さめのブロックサイズの方がベストエフォートに近いはずなので、今回はブロックサイズを4KBに設定し、以下のパターンで比較してみます。

  • ランダムリード
  • ランダムライト
  • ランダムリードライト

 パラメータ条件をfioのパラメータ設定ファイルとして準備します。

Rand-Read-4k.fio
[global]
ioengine=libaio
direct=1
ioscheduler=noop
invalidate=1
group_reporting
directory=/home
runtime=60

[Rand-Read-4k]
readwrite=randread
size=4G
bs=4k
iodepth=32
numjobs=1
Rand-Write-4k.fio
[global]
ioengine=libaio
direct=1
ioscheduler=noop
invalidate=1
group_reporting
directory=/home
runtime=60

[Rand-Write-4k]
readwrite=randwrite
size=4G
bs=4k
iodepth=32
numjobs=1
Rand-ReadWrite-4k.fio
[global]
ioengine=libaio
direct=1
ioscheduler=noop
invalidate=1
group_reporting
directory=/home
runtime=60

[Rand-ReadWrite-4k]
readwrite=randrw
size=4G
bs=4k
iodepth=32
numjobs=1

 fioを実行します。
fio -f Rand-Read-4k.fio -filename=/tmp/fio_test -output Rand-Read-4k.result
fio -f Rand-Write-4k.fio -filename=/tmp/fio_test -output Rand-Write-4k.result
fio -f Rand-ReadWrite-4k.fio -filename=/tmp/fio_test -output Rand-ReadWrite-4k.result

2. 計測結果

AWS GCP IDCF(henry) IDCF(lux)
Rand-Read-4k 3110 2749 2664 80282
Rand-Write-4k 3110 1814 2540 41347
Rand-ReadWrite-4k 1555 884 1269 27666

※ 2017/05/15の実測値です。

  • AWSはボリュームタイプをgp2にしたので、3000IOPSがバーストの上限です。これ以上のI/O性能が求められる場合は Provisioned IOPS [PIOPS]を選択するべき、ということですね。
  • IDCFの最新ゾーンであるluxは非常に高いI/O性能を持っていることがわかりました。
  • 同じIDCFでも、古いゾーンであるhenryはluxよりもだいぶディスク性能が劣ることがわかりました。でも、ディスク性能に限って言えばGCPと大差ないようです。

続きを読む

Vagrant+Amazon Linux

最新のAmazonLinuxのboxを持ってくる(Virtualbox Only)

vagrant init mvbcoding/awslinux
vagrant up --provider virtualbox

指定のバージョンをboxに追加してそれを使う

vagrant box add amzn-linux-2017.03 https://atlas.hashicorp.com/mvbcoding/boxes/awslinux/versions/2017.03.0.20170401/providers/virtualbox.box

AWS EC2 に AmazonLinux を起動してプロキシを起動

上記のみだと、AmazonLinux向けのRPMレポジトリが参照できないので、yumをプロキシするためにAWS上にAmazonLinuxのEC2を適当に起動して、tinyproxy を起動します
※ tinyproxyのデフォルトポートは 8888 です、 アクセス元のIPとかセキュリティグループで制限しておいた方がいいです

tinyproxy を EC2(AmazonLinux) へインストール

sudo yum -y install epel-release.noarch
sudo yum -y install --enablerepo=epel tinyproxy
sudo service tinyproxy start

Vagrant上のyum.confへプロキシ設定追加

echo 'proxy=http://<AWS EC2 IP ADDR>:8088' >> /etc/yum.conf

参考

続きを読む

AWSのGPUインスタンスを利用してTensorflowを試す

EC2のセットアップ

以下の通りp2.xlargeというGPUインスタンスを利用して、Ubuntuのインスタンスを構築します。
(現状、東京リージョンではp2インスタンスは利用できないため、ここでは)

  • AMIの選択: Ubuntu Server 16.04 LTS (HVM), SSD Volume Type – ami-80861296
  • インスタンスタイプの選択: p2.xlarge(GPUコンピューティング) $0.90/h
  • インスタンスの詳細の設定: デフォルトのまま
  • ストレージの追加: 64GiB 汎用SSD
  • Add Tags: なし
  • セキュリティグループの追加: 新しいセキュリティグループを作成する
    • sg_01: ssh 22, カスタムTCP 9898(jupyter用)
  • 確認と作成
    • 新しいキーペアの作成: kp_01.pem

セキュリティグループとキーペアは適当な名前を設定します。(ここでは、sg_01kp_01としています。)
また、既存のキーペアを利用しても問題ありません。

キーペアをダウンロードしたら、.sshに移動してパーミションを変更します。

$ mv ~/Download/kp_01.pem ~/.ssh/.
$ chmod 600 ~/.ssh/kp_01.pem

インスタンスが作成されたら、マネジメントコンソールでPublic DNSを確認し、SSHでログインします。

$ ssh -i ~/.ssh/kp_01.pem ubuntu@<Public DNS>

以下はEC2上での作業となります。まず、パッケージを更新しておきます。

$ sudo apt-get update
$ sudo apt-get upgrade

CUDA

CUDA 8.0のインストール

URL: https://developer.nvidia.com/cuda-downloads
Installation guide: http://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html

事前確認

CUDAに対応したGPUを搭載しているか確認

$ lspci | grep -i nvidia
00:1e.0 3D controller: NVIDIA Corporation GK210GL [Tesla K80] (rev a1)

CUDAに対応したOSか確認

$ uname -m && cat /etc/*release
x86_64
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
NAME="Ubuntu"
VERSION="16.04.2 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.2 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial

gcc(+開発ツール群)のインストール

$ sudo apt-get install build-essential

動作中のカーネルと同じバージョンのカーネルヘッダーをインストール

$ sudo apt-get install linux-headers-$(uname -r)

インストール

https://developer.nvidia.com/cuda-downloads の「Select Target Platform」で以下の通り選択するとダウンロードリンクとインストール手順が表示されます。
リンク先のURLからwgetでファイルを取得して、インストールします。
(ここではcuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64-debをインストールします。)

$ wget https://developer.nvidia.com/compute/cuda/8.0/Prod2/local_installers/cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64-deb
$ sudo dpkg -i cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64-deb
$ sudo apt-get update
$ sudo apt-get install cuda

環境変数の設定

~/.bash_profileに以下の通り設定します。

~/.bash_profile
export CUDA_HOME="/usr/local/cuda-8.0"
export PATH="${CUDA_HOME}/bin${PATH:+:${PATH}}"
export LD_LIBRARY_PATH="${CUDA_HOME}/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"

ログインし直して設定を反映します。

$ exec $SHELL -l

動作確認

サンプルプログラムをビルドして動作確認をします。(実行しなくても問題ありません。)

$ cuda-install-samples-8.0.61.sh test
$ cd test/NVIDIA_CUDA-8.0_Samples
$ sed -i "s/nvidia-367/nvidia-375/g" `grep "nvidia-367" -r ./ -l`
$ make

※sedの行は、今回のバージョンのサンプルプログラムに含まれるMakefileでドライバの指定に誤りがあったため置換しています。
(参考: https://askubuntu.com/questions/911548/cuda-examples-not-working-after-cuda-8-0-install)

cuDNN 5.1のインストール

URL: https://developer.nvidia.com/cudnn
ダウンロードにはNVIDIA Developer Programの会員登録が必要です。
認証が必要なため、ファイルをいったんローカルPCにダウンロードし、SCPでEC2にアップロードします。
(ここではcudnn-8.0-linux-x64-v5.1.tgzを利用します。)

ローカルからSCP

$ scp -i ~/.ssh/kp_01.pem ~/Downloads/cudnn-8.0-linux-x64-v5.1.tgz ubuntu@<Public DNS>:~/.

EC2上でインストール(ファイルの展開と配置のみ)

$ tar zxvf cudnn-8.0-linux-x64-v5.1.tgz
$ sudo cp cuda/include/* ${CUDA_HOME}/include/.
$ sudo cp cuda/lib64/* ${CUDA_HOME}/lib64/.

NVIDIA CUDA Profile Tools Interface(libcupti-dev)のインストール

apt-getでインストールできます。

$ sudo apt-get install libcupti-dev

ただし、今回は実行した時に”*** is not a symbolic link”というエラーが出たため、以下の通り解決しました。
(参考: http://stackoverflow.com/questions/43016255/libegl-so-1-is-not-a-symbolic-link)

$ sudo mv /usr/lib/nvidia-375/libEGL.so.1 /usr/lib/nvidia-375/libEGL.so.1.org
$ sudo ln -s /usr/lib/nvidia-375/libEGL.so.375.39 /usr/lib/nvidia-375/libEGL.so.1

$ sudo mv /usr/local/cuda-8.0/targets/x86_64-linux/lib/libcudnn.so.5 /usr/local/cuda-8.0/targets/x86_64-linux/lib/libcudnn.so.5.org
$ sudo ln -s /usr/local/cuda-8.0/targets/x86_64-linux/lib/libcudnn.so.5.1.10 /usr/local/cuda-8.0/targets/x86_64-linux/lib/libcudnn.so.5

$ sudo mv /usr/lib32/nvidia-375/libEGL.so.1 /usr/lib32/nvidia-375/libEGL.so.1.org
$ sudo ln -s /usr/lib32/nvidia-375/libEGL.so.375.39 /usr/lib32/nvidia-375/libEGL.so.1

GPU設定

http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/accelerated-computing-instances.html
を参考に”GPU 設定の最適化 (P2 インスタンスのみ)”を適用します。

$ sudo nvidia-smi -pm 1
$ sudo nvidia-smi --auto-boost-default=0
$ sudo nvidia-smi -ac 2505,875

Python環境

こちらの記事を参考にして、pyenv + minicondaの環境を作成します。
(「anaconda単独だと実は問題があります。」とのこと)

pyenv

https://github.com/pyenv/pyenv#installation

git cloneして~/.bash_profileの設定をします。

$ git clone https://github.com/pyenv/pyenv.git ~/.pyenv
~/.bash_profile
export PYENV_ROOT="${HOME}/.pyenv"
export PATH="${PYENV_ROOT}/bin:${PATH:+:${PATH}}"
eval "$(pyenv init -)"

miniconda

pyenvで最新のminiconda(ここでは、miniconda3-4.3.11)をインストールします。

$ pyenv install -l | grep miniconda
...
(省略)
...
  miniconda3-4.3.11

$ pyenv install miniconda3-4.3.11
~/.bash_profile
export CONDA_HOME="${PYENV_ROOT}/versions/miniconda3-4.3.11"
export PATH="${CONDA_HOME}/bin${PATH:+:${PATH}}"

Tensorflow

Install with Anaconda

condaでAnaconda環境を作成して、Tensorflowをインストールします。

$ conda create -n tensorflow python=3.5 anaconda
$ source activate tensorflow
(tensorflow)$ pip install --ignore-installed --upgrade https://storage.googleapis.com/tensorflow/linux/gpu/tensorflow_gpu-1.1.0-cp35-cp35m-linux_x86_64.whl

Jupyter notebook

http://jupyter-notebook.readthedocs.io/en/latest/public_server.html
EC2上で起動したJupyter notebookにローカルPCから接続するための設定をします。

  • 任意のホスト名でアクセス
  • ポート番号をデフォルトから変更(ここでは9999に設定)
  • httpsで接続
  • パスワードの設定

サーバ証明書と鍵ファイルの作成

(tensorflow)$ mkdir certificate
(tensorflow)$ cd certificate
(tensorflow)$ openssl req -x509 -nodes -days 365 -newkey rsa:1024 -keyout mykey.key -out mycert.pem

パスワードのハッシュ値作成

(tensorflow)$ python
>>> from notebook.auth import passwd
>>> passwd()
Enter password: 
Verify password:
'sha1:********'
>>> exit()

jupyterの設定ファイル作成

設定ファイルの雛形を出力

(tensorflow)$ jupyter notebook --generate-config

以下の設定を追加

~/.jupyter/jupyter_notebook_config.py
c.NotebookApp.certfile = '/home/ubuntu/certificate/mycert.pem'
c.NotebookApp.keyfile = '/home/ubuntu/certificate/mykey.key'
c.NotebookApp.ip = '*'
c.NotebookApp.port = 9999
c.NotebookApp.open_browser = False
c.NotebookApp.password='sha1:********'

Jupyter notebook起動

(tensorflow)$ jupyter notebook

ローカルPCのブラウザでhttps://<Public DNS>:9999にアクセスすると、パスワード入力画面が表示されるので、「パスワードのハッシュ値作成」の際に入力したパスワードを入力してログインします。

続きを読む

AWSでマストドンインスタンスを立てる

マストドンインスタンスを立てた。

筋トレやダイエットに関するコミュニティになれば良いと思って筋肉インスタンスを立てました。
Muscledon

AWSでマストドンを立てる

という記事を書こうかと思ったが既に完璧な手順があった。同じ内容の記事はフヨウラ!

マストドンAWS構築チュートリアル完全版|初心者から大規模運用まで

とはいえこの記事通りに進めているうちにつまったところがあるので解決法を提示する。

docker-compose buildに失敗する。

apt-getでdockerを入れるとバージョンが古く、buildに失敗してしまう。
新しいバージョンのdocker及びdocker-composeの入れ方は以下の兄貴の記事を参考にしてほしい。
DockerとDocker Composeをインストール

docker-compose buildに失敗する。

上記の対応を行っても以下のようなエラーで失敗してしまった。

ERROR: https://nl.alpinelinux.org/alpine/edge/main: operation timed out
WARNING: Ignoring APKINDEX.65bdaf85.tar.gz: No such file or directory
OK: 21 MiB in 29 packages
WARNING: Ignoring APKINDEX.65bdaf85.tar.gz: No such file or directory
WARNING: The repository tag for world dependency 'nodejs@edge' does not exist
WARNING: The repository tag for world dependency 'nodejs-npm@edge' does not exist
WARNING: The repository tag for world dependency 'imagemagick@edge' does not exist
ERROR: Not committing changes due to missing repository tags. Use --force to override.
ERROR: Service 'streaming' failed to build: The command '/bin/sh -c echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories  && BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     python     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs@edge     nodejs-npm@edge     git     libpq     libxml2     libxslt     ffmpeg     file     imagemagick@edge     ca-certificates  && npm install -g npm@3 && npm install -g yarn  && update-ca-certificates  && rm -rf /tmp/* /var/cache/apk/*' returned a non-zero code: 255

原因としては*.alpinelinux.orgのサーバがダウンしているためだ。
この記事を書いている現在2017/05/05 20:00もアクセス出来ない状態になっている。
2017/05/05 22:40現在復活している。
https://alpinelinux.org/

リポジトリの向き先をalpineのCDNに変更してやるとよい。
cloneしてきたmastodonディレクトリ直下にあるDockerfileを編集する。

Dockerfile
- RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories \
+ RUN echo "@edge http://alpine.gliderlabs.com/alpine/edge/main" >> /etc/apk/repositories \

ユーザー数が0人と表示される。

何故か「このインスタンスについて」のユーザー数が0人と表示されていた。
原因はMastodonアプリとDBのタイムゾーンが異なっているためだった。
DBは日本時間だったがEC2インスタンスはUTCだったので日本時間に合わせる。

mastodon/config/application.rb
-    # config.time_zone = 'Central Time (US & Canada)'
+    config.time_zone = 'Tokyo'

HTTP/2に対応させる。

nginxでもいじってHTTP/2に対応させてやるか!
と思っていたら既にHTTP/2になっていた。どうやらAWSのロードバランサでapplicationを選択するとHTTP/2になるようだ。

サイトの管理画面を出す。

ここに書いてある通りなのだが、
まずユーザーを作って(ここではaliceというユーザとしよう)メール認証を済ませる。
そして以下のコマンドを打ち込むのだが

RAILS_ENV=production bundle exec rails mastodon:make_admin USERNAME=alice

では上手くいかなかった。
かわりに

sudo docker-compose run --rm web rails mastodon:make_admin USERNAME=alice

ならうまくいった。:relaxed:

mastodonのdaily taskを追加する。

こちらの兄貴が書いている通りmastodon:dailyを実行してやらないと
国交を断絶されてしまうらしい。cronにタスクを追加してカイコクシテクダサーイ

Postgres Redis ストレージを切り離す。

投稿画像、動画をS3に保存するように変更する。

こちらの兄貴のやり方に従って作業すれば良い。
ただしS3を使っているのであれば以下の設定は不要(検証済み)
S3の代わりに別のサーバを使う場合は必要らしい。

.env.production
# S3_PROTOCOL=
# S3_ENDPOINT=

mastodonは変化し続ける可能性の獣

mastodonのGithubを見ると最終コミットが数時間前だったりする。
頻繁に変化し続けているのでちょいちょいバグる。二回ほど構築してみたが、一回目は管理画面からサイトの編集が出来なかったが、二回目は出来るようになった。
今のところCloneするタイミングによってかなり挙動が変わるようだ。

とかUCな気持ちに浸っていたら公式にはこう書いていた。

Please note: It is highly recommended to run a tagged release of Mastodon and not run off the current master branch.

訳:間違ってもMasterのブランチからクローンなんかすんじゃねーぞ。

続きを読む

IoTでペットヘルスケア[実装編:センサデータの取得~AWS IoT連携]

本記事で取り扱うこと

IoTでペットヘルスケア[構想編]
IoTでペットヘルスケア[実装編:センサデータの取得~AWS IoT連携] ←本記事(構想編の続編)です
IoTでペットヘルスケア[実装編:AWSサービス間の連携]←次回です

IoTでペットヘルスケア[構想編]で紹介したアーキテクチャのうち、下記の赤枠内の実装(センサデータの取得からAWS IoTへデータを取り込むまでの部分)について、順を追って説明します。手順は現時点(2017/5)のものであるため、時間が経過している場合には、より便利なものが出ている可能性もありますので最新の情報をご確認ください。

AWS Design_pets_healthcare_実装編01.png

Raspberry Pi 3の準備

利用機材

以下を準備します。

  • Raspberry Pi3 Model B
  • GrovePi+
  • Grove – PIR Motion Sensor
  • microSDカード(今回は16GB)
  • USB Micro B(給電用)
  • その他(セットアップ用)
    • HDMIケーブル
    • モニタ(HDMI対応)
    • USB接続マウス
    • USB接続キーボード
    • microSDカード・リーダライタ

その他以外を接続すると、以下のような状態になります。
raspberrypi.jpg

OSのインストール

RASPBIAN JESSIE WITH PIXELをインストールします。
シンプルにインストールするだけですが、以下のとおりです。

  1. RASPBIAN JESSIE WITH PIXELをダウンロード、解凍します。

    • 今回は以下のリリースを利用。

      • Version:April 2017
      • Release date:2017-04-10
      • Kernel version:4.4
  2. 解答したイメージファイルをmicroSDカードへDDで書き込む。

  3. microSDカードをRaspberry Piへ挿入、セットアップ用のその他機器を接続して給電用USBケーブルを接続する。

[オプション] 日本語環境を設定

初期インストール時は英語環境になっているため、必要であれば日本語環境化しましょう。

  1. OS起動後、MenuからRaspberry Pie Configurationをクリックします。

    • このあたりのメニューの名前はOSのバージョンで少しずつ変わっていますので、それらしいものをお探しください。
  2. Localisationのタブを選択し、以下を設定する。(※設定後、再起動を促されますが、Noを選択する)

    • Locale

      • Language: ja(Japanese)
      • Country: JP(Japan)
      • Character Set: UTF-8
    • Time zone
      • Area: Japan
    • Keyboard
      • Country: Japan
      • Variant: Japanese
  3. 日本語フォントをインストールする。
    • ログイン時のデフォルトユーザ(pi)でターミナルを開き、下記を実行します。
$ sudo apt-get install jfbterm

上記の手順を実行後、OSを再起動すれば日本語化されます。

[オプション] エディタのインストール

好みの問題かもしれませんが、初期のviは使いづらいためvimをインストールします。

$ sudo apt-get update
$ sudo apt-get install vim

[オプション] tmpのRAMDISK化

長期間の稼働を前提とするため、念のためにファイルI/Oが継続的に行われる部分についてはRAMDISK化しておきます。(そんなに大量に読み書きするわけではないため、気休め程度ですが。)

/etc/fstabに以下を追記し、再起動後に正常にマウントされていることを確認します。

tmpfs           /tmp            tmpfs   defaults,size=32m 0       0
tmpfs           /var/tmp        tmpfs   defaults,size=16m 0       0
tmpfs           /var/log        tmpfs   defaults,size=32m 0       0

ネットワーク接続

Raspberry Piをインターネットへ接続できるように設定します。
有線でも無線でも、お好きなほうでOKですが、設置場所を考えると無線のほうが取り回しが楽なのでいいかもしれません。GUIからも設定できるため、特に難しいことはないので説明は割愛します。

GrovePiのライブラリをインストール

GrovePiとセンサーを利用するためのライブラリを導入します。

  • GrovePiのライブラリをクローンしてインストール
$ mkdir /home/pi/grovepi
$ cd /home/pi/grovepi
$ sudo git clone https://github.com/DexterInd/GrovePi
$ cd /home/pi/grovepi/GrovePi/Script
$ sudo chmod +x install.sh
$ sudo ./install.sh

途中でインストールの継続を聞かれるのでY(Yes)を選択。

  • GrovePi+の接続確認。
    GrovePi+が接続されている状態で、以下のコマンドを実行します。
$ sudo i2cdetect -y 1

WS000004.JPG
上記のように04が見えていれば、正常にGrovepi+を認識できています。

PIR Motion Sensorのデータ取得

  • PIR Motion SensorをD8ポートに接続します。

接続ポートは他でもOKですが、次項のサンプルコードではD8前提となっています。

  • センサデータ取得のサンプルコード

0.5秒毎にセンサデータの取得(動きを検知)して、1分ごとの集計結果をファイルに出力するサンプルです。デーモン化する前提ですので、不要な部分はコメントアウトしてあります。動作確認時はコメントアウトを外してprint部分をすれば、0.5秒おきの検知結果を標準出力へ出します。
#長期間運用する場合は、デーモン化してしまうのでsyslogが溢れないようにコメントアウトしたままのほうが良いと思います。

grove_pir_motion_sensor_d.py
import os
import sys
import time
import grovepi
import datetime

def getMotionSensor():
    pir_sensor = 8
    motion=0
    grovepi.pinMode(pir_sensor,"INPUT")
    countPerMin = 0
    i = 0

    while True:
        try:
            if i >= 120:
                d = datetime.datetime.today()
                output = "{ " + "\"countPerMin\":" + str(countPerMin)    + ",\"timestamp\":\"" +d.strftime("%Y-%m-%dT%H:%M+09:00")+ "\" }"
                print output

                f = open('/tmp/motionDetected.json', 'w')
                f.write(output)
                f.close()                     

                i = 0
                countPerMin = 0
            else:
                i = i + 1

        # Sense motion, usually human, within the target range
            motion=grovepi.digitalRead(pir_sensor)
            if motion==0 or motion==1:  # check if reads were 0 or 1 it can be 255 also because of IO Errors so remove those values
                if motion==1:
                    countPerMin += 1
                    #print ('Motion Detected')
                    #print i
                #else:
                    #print ('-')
            time.sleep(.5)

        except IOError:
            print ("Error")

def fork():
        pid = os.fork()

        if pid > 0:
                f = open('/var/run/motionsensor.pid','w')
                f.write(str(pid)+"\n")
                f.close()
                sys.exit()

        if pid == 0:
                getMotionSensor()


if __name__=='__main__': 
    fork()   
  • テスト実行

    • ハードウェアのPinを読みに行くため、root権限での実行が必要です。
    • うまく動作すれば、/tmp/motionDetected.jsonへ以下のような内容が出力されます。
{ "countPerMin":0,"timestamp":"2017-05-05T14:47+09:00" }

こちらのセンサーは、もう少し短い間隔でデータを取得することも可能ですが、ネコ様がトイレに入って出てくるまでの動きを検知したいので、0.5秒おきに検知を行い、1分間(最大120回検知)の中でどのくらい動きがあったのかを返すためのデータ出力を行っています。最終的には、この検知回数を元に通知を行っていきます。

純粋にセンサーしかない場合はともかくとして、ラズパイやEdisonなどの処理能力があるものであれば、センサーの生データそのものよりも、少し加工して使いやすくしたものを作り出したほうがよいのでは、と思います。

データ取得スクリプトのデーモン化

データ取得のスクリプトをOS起動時やプロセス停止時に自動で起動するため、デーモン化します。
#最近はinit.dから変わっていますので、久しぶりに触る方はご注意ください。・・・一瞬、困ったのは私だけ?w

  • 上記のgrove_pir_motion_sensor_d.pyを/usr/local/libに配置します。
  • /etc/systemd/system に motionsensord.serviceを作成します。
motionsensord.service
[Unit]
Description=Pir Motion Sensor Daemon
[Service]
ExecStart=/usr/local/lib/grove_pir_motion_sensor_d.py
Restart=always
Type=forking
PIDFile=/var/run/motionsensor.pid
[Install]
WantedBy=multi-user.target
  • サービス設定を読み込み、手動でデーモンを起動して動作確認します。

設定のリロード、手動起動

$ sudo systemctl daemon-reload
$ sudo systemctl start motionsensord

デーモンのステータスやファイル出力を確認

$ sudo systemctl status motionsensord

出力結果の例
WS000005.JPG

  • 自動起動を有効にします。
$ sudo systemctl enable motionsensord

OS再起動後も動作していれば正常に設定できています。

AWS IoTの準備

1年ほど前と比較して、非常に簡単になっています。基本はAWS IoTに示されれる手順に沿って実行するだけです。
#この説明いらないのでは・・・と思いつつ、AWS IoT初学者の方もいらっしゃると思いますので、一通りの流れを紹介ということで。

デバイスの登録

Raspberry PiをAWSに接続するための設定を行います。

connect_01.jpg
AMCからAWS IoTのコンソールを開き、Connect のページを選択します。Configuring a deviceの”Get started”を選択します。

WS000008.JPG
接続までの流れ説明ページが表示されるので、”Get started”を選択します。

WS000009.JPG
環境に応じてSDKをセットアップするためのKitが準備されていますので、利用したいものを選択します。今回はRaspbianなのでLinux/OSXを選択します。また、AWS IoTへの部分を記述する際の言語も選択します。今回はNode.jsを利用します。最後に”Next”を選択します。

WS000010.JPG
AWS IoT上で管理するデバイスの名前を指定します。こちらは、管理者がどのデバイス(今回だとRaspberry Piのどのマシンなのか)が分かればよいため、任意の名前でOKです。指定後、”Next step”を選択
※どこかに設定した値と一致させなければならない、といったことはありません。

WS000011.JPG
接続用Kitのダウンロードを行います。内容を確認後、”Next step”を選択します。

ちなみに、ここまでの操作により、以下の手順が自動的に行われています。(1年前は個別に行う手順でしたので関係が分かりやすかったのですが、今は便利になった反面、何が行われているのか少々分かり難くなっています。)

  • デバイスがAWS IoTにデータを送る権限の定義(ポリシー作成)

    • A policy to send and receive messages の部分です
  • デバイスに配置する認証キーの作成
    • A certificate and private key の部分です。Kitに含めれます。
  • デバイスと認証キー、ポリシーの関連付け
    • 3つを関連付け、特定のデバイスに権利を持たせて認証し、AWS IoTのトピック(MQTTでつかうチャネルのようなもの)へPublish/Subscribeできる状態とします。

デバイスの設定

WS000012.JPG
ダウンロードしたファイルをデバイス(Raspberry Pi)へ転送し、示されている手順を実行します。
※こちらの画面は接続確認にそのまま利用しますので、開いたままにしておいてください。

start.shを実行すると、以下の処理が実行されます。

  • 証明書の確認
  • aws-iot-device-sdkをはじめ、各種モジュールのインストール
  • AWS IoTとの接続テスト
start.sh
start.sh
# stop script on error
set -e

# Check to see if root CA file exists, download if not
if [ ! -f ./root-CA.crt ]; then
  printf "\nDownloading AWS IoT Root CA certificate from Symantec...\n"
  curl https://www.symantec.com/content/en/us/enterprise/verisign/roots/VeriSign-Class%203-Public-Primary-Certification-Authority-G5.pem > root-CA.crt
fi

# install AWS Device SDK for NodeJS if not already installed
if [ ! -d ./node_modules ]; then
  printf "\nInstalling AWS SDK...\n"
  npm install aws-iot-device-sdk
fi

# run pub/sub sample app using certificates downloaded in package
printf "\nRuning pub/sub sample application...\n"
node node_modules/aws-iot-device-sdk/examples/device-example.js --host-name=av8pngo3zyi88.iot.ap-northeast-1.amazonaws.com --private-key=y_raspberrypi_01.private.key --client-certificate=y_raspberrypi_01.cert.pem --ca-certificate=root-CA.crtroot@raspberrypi:~/workspace/cat_healthcare/gitrepo/connect_device_package#

デバイスに接続し、start.shを実行すると、下記のようにconnectと表示されれば接続が成功しています。
WS000015.JPG

さきほどのAWS IoTの画面に戻ると、Step3の下に、デバイスからPublishされたメッセージが届いているのが分かります。これはstart.shから実行された接続確認用のコードから送られています。
WS000013.JPG

続いて、Step 4のテキストボックスに任意の文字列を入れ、Send messageを選択すると、デバイス(Raspberry Pi)側がSubscribeしているTopicにPublishすることができます。今回はテスト用に”test publish message AAAA”と送ってみました。すると、以下のようにデバイス側で受信することができています。
WS000014.JPG

以上でデバイス側の設定は完了です。

センサデータ取得の連携設定

取得したセンサーのデータを実際にAWS IoTへ送信します。

AWS IoTへのPublish

サンプルですので、べた書き部分が多いですが、以下のようなスクリプトを作成します。今回はconnect_device_packageを利用しやすくるため、同ディレクトリに配置してしまいます。

publish_data_pirmotion.js
var awsIot = require('aws-iot-device-sdk');
var fs = require('fs');

var clientId_suffix = 'MotionSensor01';
var deviceId = 'y_raspberrypi_01';
var sensor = 'motion_sensor';
var MQTT_clientID = deviceId + "_" + clientId_suffix;

var topicname = "topic/" + deviceId + "/sensor/" + sensor;

var device = awsIot.device({
    keyPath: 'y_raspberrypi_01.private.key',
    certPath: 'y_raspberrypi_01.cert.pem',
    caPath: 'root-CA.crt',
    clientId: MQTT_clientID,
    region: 'ap-northeast-1'
});


device.on('connect', function () {

    console.log('connect');
    setInterval(function () {

        var messageJson = JSON.parse(fs.readFileSync('/tmp/motionDetected.json', 'utf8'));
        messageJson.clientId = MQTT_clientID;
        messageJson.deviceId = deviceId;

        var message = JSON.stringify(messageJson);

        console.log("Publishing.. " + topicname);
        device.publish(topicname, message);
    }, 60000);
});

処理としては、モーションセンサのデータ取得結果(motionDetected.json)に対してdeviceIdやclientIdをデータとして含め、1分毎にAWS IoTへ送信しているだけになります。

注意点としては、MQTTの仕様として、同一のトピックに対して接続する際にclientIdごとにコネクションを確立しており、複数のデバイス間で重複していると、セッションの奪い合い(お互いに接続、接続断を繰り返してしまう)ことになります。そのため、一意になるようにclientIdを生成して上げる必要があります。今回はデバイスの名前にセンサーの名前を組み合わせて生成しました。

MQTTのトピック名についても、センサーの種類ごとに分けて生成していく想定になっています。センサーの種類を増やす際には、これらのスクリプトやトピックごと増やすアーキテクチャとなります。AWS IoT側からデータを利用する際は、このトピック名を対象として選択することになるため、複数種類のデータを取り扱う際には、十分に検討して設計が必要です。例えば、途中のパスによって値のグルーピングを行う等。

動作確認は、AWS IoTのテスト用コンソールを利用します。
WS000017.JPG
AWS Iotの”Test”ページを開き、トピック名を入力して”Subscribe to topic”を選択します。

すると、Subscriptionsの中に指定したトピック名が表示されますので、選択します。1分毎にデータを送っているため、成功していれば、以下のようにメッセージを受信(Subscribe)できます。
WS000019.JPG

以上でモーションセンサーで実際に取得したデータをAWS IoTへ連携することができるようになりました。

Publish用スクリプトのデーモン化

publish_data_pirmotion.jsもセンサデータ取得用のスクリプトと同様にデーモン化してしまいます。昔はnode.jsだとforeverなどを利用していた気がしますが、systemdの場合は、Pythonスクリプトと同様でOKです。

  • /etc/systemd/system に publish_motionData.serviceを作成します。
publish_motionData.service
[Unit]
Description=publish motion data
After=syslog.target network.target
[Service]
Type=simple
ExecStart=/root/.nvm/versions/node/v6.1.0/bin/node  publish_data_pirmotion.js
WorkingDirectory=/root/workspace/cat_healthcare/gitrepo/connect_device_package
KillMode=process
Restart=always
[Install]
WantedBy=multi-user.target

#rootで作成しちゃっていますので、上記のような内容になっています。

  • サービス設定を読み込み、手動でデーモンを起動して動作確認します。

設定のリロード、手動起動

$ sudo systemctl daemon-reload
$ sudo systemctl start publish_motionData

デーモンのステータスやファイル出力を確認

$ sudo systemctl status publish_motionData
  • 自動起動を有効にします。
$ sudo systemctl enable publish_motionData 

OS再起動後もAWS IoTへデータが送られていることを確認できればOKです。

センサーの設置

Cat’s Restroomにセンサーを設置します。

我が家では、以下のように個室風のトイレが設置されています。
Cat'sRestroom_外観.jpg
少々わかりにくいのですが、上記の写真のようになっています。2つのカラーボックスを棚として開いている側を向き合わせて置き、片方の背板を出入り口用に切り取っています。中は市販のネコトイレ(すのこ式)です。
#ホワイトペレット最高!

その中に対して、以下のようにセンサーを設置しました。図は真横から見たときの断面図・・・絵心なさすぎてすいません。
RestroomSensor01.png

入り口の背板(上部)の内側にモーションセンサーを取り付け、トイレ内部の動きを検知するようにしています。配線は2つのカラーボックスの間から通して、ネコ様の出入りの邪魔にならないようにしています。また、トイレ内の上部を中心に検知する配置のため、トイレ掃除等を誤検知しにくく・・・もなっているはずです。
#誤検知については、AWS IoT側で受け取ったデータを処理する際に条件付け(しきい値判定など)を行うことでも回避しています。

この配置で、0.5秒に一回のセンサーデータ取得を行うと、ネコ様がトイレに入るとバッチリ検知ができるようになりました。

おわりに

今回、センサデータを取得してAWS IoTへデータ連携を行うところまでできるようになりました。次回はAWS IoT側で受け取ったデータを活用して召使いへ連携していく部分について記載したいと思います。

また、他にもセンサーはあるのですが、長くなりすぎるため別立てして拡張編でも書いたほうがよさそうです。

次回:IoTでペットヘルスケア[実装編:AWSサービス間の連携]
cat01.jpg

続きを読む

TensorFlow with GPU on Docker を AWS で起動する

構成

https://github.com/NVIDIA/nvidia-docker にある以下の図が分かりやすい。

図

今回、Server は AWS の p2 インスタンス (GPU インスタンス)。
Host OS は Ubuntu 16.04 を利用する。

手動でインストールが必要なものは以下の通り。

  • CUDA Toolkit / CUDA Driver

    • NVIDIA GPU をコントロールするために必要
    • 2つ同時にインストールされる
  • Docker Engine
  • nvidia-docker
    • Docker コンテナ内から CUDA Toolkit 経由で GPU を触るために必要

1. AWS インスタンス起動

GPU インスタンスの p2 系を起動する。

AMI
Ubuntu Server 16.04 LTS (HVM), SSD Volume Type
備考
ディスクサイズは 100 GB に変更する (デフォルトは 8 GB、足りない)

2. CUDA のインストール

公式ドキュメント 通りに進める。
ただ、ドキュメントが長いので読まない方が良い。ハマると果てしなくハマって辛い。

実際に必要なのは3箇所のみ。

  • “2. Pre-installation Actions” > “2.6. Download the NVIDIA CUDA Toolkit”
  • “3. Package Manager Installation” > “3.6. Ubuntu”
  • “6. Post-installation Actions” > “6.1.1. Environment Setup”

実際のコマンドは以下の通り。

## 2.6 Pre-installation Actions (Download the NVIDIA CUDA Toolkit)
$ wget https://developer.nvidia.com/compute/cuda/8.0/Prod2/local_installers/cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64-deb

## 3.6 Package Manager Installation (Ubuntu)
$ sudo dpkg -i cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64-deb
$ sudo apt-get update
$ sudo apt-get install cuda

## 6.1.1 Post-installation Actions (Environment Setup)
$ echo 'export PATH=/usr/local/cuda-8.0/bin${PATH:+:${PATH}}' >> ~/.bashrc
$ source ~/.bashrc

nvcc が入れば成功。

$ nvcc --version
nvcc: NVIDIA (R) Cuda compiler driver
Copyright (c) 2005-2016 NVIDIA Corporation
Built on Tue_Jan_10_13:22:03_CST_2017
Cuda compilation tools, release 8.0, V8.0.61

3. Docker のインストール

公式ドキュメント (Install using the repository) 通りに、Docker CE をインストールする。

インストール完了したら、sudo 無しで動作するよう ubuntu ユーザを docker グループに追加して、SSH ログインし直す。

$ sudo usermod -aG docker ubuntu

hello-world が動けば完了。

$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
78445dd45222: Pull complete
Digest: sha256:c5515758d4c5e1e838e9cd307f6c6a0d620b5e07e6f927b07d05f6d12a1ac8d7
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.
...

4. nvidia-docker のインストール

公式ドキュメント 通りに進める。

“Quick start” > “ubuntu distributions” のコマンドを実行すればOK。

$ wget -P /tmp https://github.com/NVIDIA/nvidia-docker/releases/download/v1.0.1/nvidia-docker_1.0.1-1_amd64.deb
$ sudo dpkg -i /tmp/nvidia-docker*.deb && rm /tmp/nvidia-docker*.deb

以下のコマンドで Docker コンテナがホスト (p2 インスタンス) の GPU を認識していることが確認できる。

$ nvidia-docker run --rm nvidia/cuda nvidia-smi
Sun Apr 23 06:10:55 2017
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 375.39                 Driver Version: 375.39                    |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Tesla K80           Off  | 0000:00:1E.0     Off |                    0 |
| N/A   47C    P8    28W / 149W |      0MiB / 11439MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID  Type  Process name                               Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

TensorFlow

あとは TensorFlow でもなんでもコンテナ内から GPU が触れる。

https://hub.docker.com/r/tensorflow/tensorflow/

$ nvidia-docker run -it -p 8888:8888 tensorflow/tensorflow:latest-gpu

続きを読む