2015/11/12

Waveshare 3.5 Spotpear TFTパネルをRPi2に付けて・・

cocopar™-3-5インチタッチパネル をRaspberry-Pi2/bに繋いでみてからのこと。

何も考えないでやると、そのページ情報から「英文の説明書、インストールファイルは、
https://www.dropbox.com/s/2s02o4jgvnexdov/3.5inch%20RPi%20LCD.7z?dl=0」と書かれているので、素直にダウンロードして(これはセットアップ済みのRasbianイメージ)SDに焼いて起動するんだそうな。

製造メーカのWaveshareのページ へ行くと、やはりRasbianイメージと若干のツールという形で説明されている。

とりあえず、どっちもやってみて、どっちもちゃんと動きましたが、カーネルが違ってたり、OSアップデートするとだめとか書かれてて・・使えねーっす。

ということで調べたら、ちゃんといいのがあるじゃないですかぁ
https://github.com/notro/fbtft/wiki
を読んで、ふむ。色んなLCDデバイスに対応しているらしいと。しかも現在はRaspberry Pi Foundation kernelに取り込まれているので、raspi-updateってやればモジュールが入る。

https://github.com/notro/fbtft/issues/215
ここがWaveshare 3.5 Spotpearについての記事。冒頭では、ああ・・Waveshareはうまくいかないのか?と不安になりつつ読み進めると汎用のflexfbを使ってやれば大丈夫と。
ホ。でも面倒なのね。

$ modprobe flexfb width=320 height=480 regwidth=16 init=-1,0xb0,0x0,-1,0x11,-2,250,-1,0x3A,0x55,-1,0xC2,0x44,-1,0xC5,0x00,0x00,0x00,0x00,-1,0xE0,0x0F,0x1F,0x1C,0x0C,0x0F,0x08,0x48,0x98,0x37,0x0A,0x13,0x04,0x11,0x0D,0x00,-1,0xE1,0x0F,0x32,0x2E,0x0B,0x0D,0x05,0x47,0x75,0x37,0x06,0x10,0x03,0x24,0x20,0x00,-1,0xE2,0x0F,0x32,0x2E,0x0B,0x0D,0x05,0x47,0x75,0x37,0x06,0x10,0x03,0x24,0x20,0x00,-1,0x36,0x28,-1,0x11,-1,0x29,-3
$ modprobe fbtft_device rotate=90 name=flexfb speed=16000000 gpios=reset:25,dc:24
ってやってモジュールを入れると、/dev/fb1 が出来上がる。
/usr/share/X11/xorg.conf.d/99-fbturbo.confの/dev/fb0を/dev/fb1にして
$ startx
するとLCDにデスクトップが出てくる。

うん。これでいいかと思ったら、記事の中に
https://github.com/swkim01/waveshare-dtoverlays
が紹介されている。素晴らしい!これなら簡単です。OSやカーネルを汚さないで済みます。

素のRasbianイメージからのスタートだとしても
$ sudo apt-get update
$ sudo apt-get upgrade
$ sudo raspi-configでSPIイネーブルにする
$ sudo rpi-update
$ sudo reboot

$ git clone https://github.com/swkim01/waveshare-dtoverlays.git

$ cd waveshare-dtoverlays
$ sudo cp waveshare-dtoverlays/waveshare35a-overlay.dtb /boot/overlays/
/boot/config.txt のdtparam=spi=onの次に
dtoverlay=waveshare35a
を追記してリブート。起動するとLCDが初期化されてバックライトが消えた状態に。

/usr/share/X11/xorg.conf.d/99-fbturbo.confの/dev/fb0を/dev/fb1に変更して
$ startx
すればLCD上でX11が立ち上がる。

https://github.com/notro/fbtft/wiki は最近更新された。fbtft_deviceを使うとDeviceTreeOverlay
を使うよりもっと簡単!っていう話だ。
が、残念ながら手持ちのwaveshare-35aはまだサポートされていないようだ。waveshare-32bならサポートされているらしい。

因みに、タッチスクリーンが付いているのだが、キャリブレーションはやってない。

2015/10/20

Mathematica for RPi2 でGPIOのSPIデータプロット&Webで確認

RaspbianにはMathematicaが入っている。今回はこのMathematicaからSPIデータを取得してプロットしてみようという試み。

組み込みAPIでは1か0だけ
Mathematica/Wolfram言語にGPIOへアクセスする方法が書かれている。 →GPIO
標準APIでは1と0しか扱えないようだ。

SPI/I2C/PWMのためには?
MathLinkというプラグイン的なものを使うことで出来そうだ。
Wolframドキュメントセンター上では、MathLinkは WSTP に置き換えられているが、自分のRasbianでは、、mathlinkのまま。最新を取ってくるとまた違うかも。


MathLink開発環境
/opt/Wolfram/WolframEngine/10.0/SystemFiles/Links/MathLink/DeveloperKit/Linux-ARM
に、必要なライブラリやサンプルプログラムが入っている。情報はこれだけで十分。

WiringPi
下回りのライブラリとして、GPIO制御の定番らしいWiringPiを使う。
$ sudo apt-get install wiringpi
で入れてもいいと思うけど、最新がいいらしいので
$ git clone git://git.drogon.net/wiringPi
$ cd wiringPi
$ sudo ./build 
で入れる。

やってみたこと
お試しで買った工作キットに、mcp3002(ADC)とフォトトランジスタ、温度センサーが入っていたので、SPIにmcp3002をつけ、mcp3002にフォトトランジスタと温度センサーをつけて、部屋の明るさと温度をプロットしてみる。そして、それをWebで見れるようにする。という感じ。


SPIプログラム(mcp3002.c)
#include 
#include 
#include 

#define PINBASE 64
#define SPI_CH  0
#define TO_VOLT (3.3f/1024)

extern float spi(int ch)
{
  return TO_VOLT * analogRead(PINBASE + ch);
}

int main(int argc, char* argv[])
{
  mcp3002Setup(PINBASE,SPI_CH);
  return MLMain(argc, argv);
}
SPIデータを取得するMathLinkプログラムはこんな感じ。WiringPiのmcp3002 API使うと非常に簡素だ。MathLink対応はmathlink.hをインクルードして、mainでMLMainを呼ぶようにするだけで良い。

MathLinkライブラリ化(mcp3002.tm)
float spi P((int));

:Begin:
:Function:       spi
:Pattern:        Spi[ch_Integer]
:Arguments:      { ch }
:ArgumentTypes:  { Integer }
:ReturnType:     Float
:End:

:Evaluate: Spi::usage = "Spi[ch] gives the SPI value."
単にプログラムをコンパイルするだけではダメで、上のようなテンプレートファイルを作成して、 mprep ツールで tmからCに変換したものをセットでビルドする。

uuidライブラリがなくて、リンク時にこけたので
$ sudo apt-get install uuid-dev
で入れた。

Makefile
CADDSDIR = /opt/Wolfram/WolframEngine/10.0/SystemFiles/Links/MathLink/DeveloperKit/Linux-ARM/CompilerAdditions
INCDIR = ${CADDSDIR}
LIBDIR = ${CADDSDIR}

MPREP = ${CADDSDIR}/mprep
CXX = /usr/bin/c++
RM = rm

EXTRA_CFLAGS= -O2

PROGRAM = mcp3002
OBJS = $(PROGRAM).o $(PROGRAM)tm.o

all : $(PROGRAM)

$(PROGRAM) : $(OBJS)
        ${CXX} ${EXTRA_CFLAGS} -I${INCDIR} $(OBJS) -L${LIBDIR} -lwiringPi -lML32i4 -lm -lpthread -lrt -lstdc++ -ldl -luuid -o $@

.c.o :
        ${CXX} -c ${EXTRA_CFLAGS} -I${INCDIR} $<

$(PROGRAM)tm.c : $(PROGRAM).tm
        ${MPREP} $? -o $@

clean :
        @ ${RM} -rf *.o *tm.c $(PROGRAM)
makeビルドすると、mcp3002という実行プログラムが出来上がる。
これをMathematicaでInstallで読み込むと、Spi[0]とSpi[1]でCのspi(int ch)を呼べるようになる。

Mathematicaスクリプト(spi.m)
(* ::Package:: *)

(* Load 'mcp3002' MathLink Module *)
Install["mcp3002"]


(* Function *)
lux := {DateList[], Spi[0] * (100/(7500*0.000033))}
deg := {DateList[], (Spi[1]-0.5)*100}

(* Data List *)
luxData = {}
degData = {}
interval = 5 * 60;


(* Plot *)
(* Lux *)
luxNow = Dynamic[Last[AppendTo[luxData,lux];
             If[Length[luxData]>144,luxData=Drop[luxData,1]];
             luxData],
        UpdateInterval->interval, TrackedSymbols->{} ]
luxPlot = Dynamic[ DateListPlot[luxData, Joined->True, PlotLabel->"Lux"], SynchronousUpdating->False]

(* Tempeture *)
degNow = Dynamic[Last[AppendTo[degData,deg];
             If[Length[degData]>144,degData=Drop[degData,1]];
             degData],
        UpdateInterval->interval, TrackedSymbols->{} ]
degPlot = Dynamic[ DateListPlot[degData, Joined->True, PlotLabel->"Temp"], SynchronousUpdating->False]

(* Export *)
Dynamic[
        Export["lux_now.gif",luxNow];
        Export["lux_plot.gif",luxPlot];
        Export["temp_now.gif",degNow];
        Export["temp_plot.gif",degPlot];
        Share[],
        SynchronousUpdating->False,
        UpdateInterval->interval]
Install["mcp3002"]でMathLinkライブラリを読み込み。これを実行すると実際にmcp3002プログラムがプロセスとして起動する。プロセス間通信でやりとりしているようだ。
その後、明るさ(lux)と温度(deg)関数を定義。Spi[0]とSpi[1]で取得したデータを変換している。

Dynamic[]を使って、5分毎にSPIデータをサンプリングして、Dateリスト型の変数へ追加していく、 さらにそれをプロットする。Mathematicaノートブック上でプロット結果が見れるようになる。

最後が、それぞれの出力結果をGIFイメージへExportする処理。このイメージをWebServerで見れるようにすれば、外のブラウザで結果を見ることができるようになる。

実行
vncサーバーでも立てて、繋いで、プログラムとスクリプトがあるディレクトリから
$ mathematica spi2.m
で起動して、右上の「Run Package」を実行すると、プロットなどのDynamic処理が5分ごとに更新される。

これはこれで問題がある、後述の「考察」が解決策。


軽量Webサーバー立てる
$ sudo apt-get install lighttpd
$ sudo lightly-enable mod userdir
$ sudo /etc/init.d/lighttpd force-reload
で、~/public_htmlにindex.html書いて、http://(pi2)/~pi/ で見れるようになる。
~/public_htmlにmathematicを実行しているカレントフォルダのリンクを貼ってgifへアクセスできるように。
あとは、適当にindex.htmlを用意。

ブラウザからアクセスしてみた結果がこちら
こちらも5分毎に更新される。

2015/10/16

snappy on kvm on lxc

RaspberryPi2でUbuntu Core Snappyは普通に動いた。が、アプリ作らないと面白く無さそうだし、ラズパイはまだまだRaspbianで遊びたいので、Snappy環境を別に作ろうと思った。
SnappyのKVMイメージが提供されているので、それで出来そうだ。

KVM on LXC

すでにLXCコンテナで環境構築しているので、その上にKVM仮想環境を動かせばいいかと。
作って動かしてみることにした。
ホストでLXCと平行してKVMも動かすのも可能だろうけど、その方が複雑な気がする。

KVMを動かすためのコンテナ作成
$ sudo lxc-clone master -n kvm
分かりにくいかもしれんけど、'kvm'という名前のlxcコンテナにした。
新規作成は面倒なので、いつもmasterコンテナをクローンする方法で作る。

kvmコンテナ設定
色々試行錯誤した結果、kvmをlxc内で動かすには以下のデバイスが必要。

/dev/net/tun
ブリッジネットワーク構築時に/dev/net/tunにアクセスするらしく、mknodで作ったのではダメで、ホストの/dev/netをマウントする必要があった。

/dev/kvm
は、kvmが稼働するときにアクセスするものだそうで、これはmknodで作ればよいらしい。

それを作ってアクセスできるように、コンテナのconfファイルに以下を追記
# device
lxc.cgroup.devices.allow = c 10:232 rwm
lxc.cgroup.devices.allow = c 10:200 rwm
lxc.mount.entry=/dev/net dev/net none bind,optional,create=dir
lxc.hook.autodev = /var/lib/lxc/kvm/mount-hook.sh
 /var/lib/lxc/kvm/mount-hook.shの中身
#!/bin/sh
mknod -m 600 ${LXC_ROOTFS_MOUNT}/dev/kvm c 10 232
コンテナ起動
$ sudo lxc-start -n kvm
で、/dev/kvm、/dev/net/tunがちゃんとできていることを確認する。

あとは、sshなりconsoleなりからコンテナにログインしての作業

kvmインストール

$ sudo apt-get install kvm virt-manager libvirt-bin bridge-utils
$ sudo apt-get install lxde tightvncserver fonts-ipafont
$ mkdir .vnc
$ ln -s /etc/X11/Xsession .vnc/xstartup

まとめて書いちゃいましたが、lxde以降はVNCデスクトップ構築のためです。kvmのvirt-managerや本題のSnappyアプリがディスプレイを必要とする場面があるため。

この時点で、libvirtdやlxcと同じようにdnsmasqが動いていればOK。
ifconfigしたときには、virbr0ブリッジインターフェースが出来ているはず。最初、これが作られなくて悩んだ。
自分はデフォルトのままでいいので、kvmネットワークは 192.168.122.0 である。lxcが10.0.3.0なので、10.0.2.0とかにした方が綺麗かもしれないけど。

UbuntuCore Snappy 取得と起動

https://developer.ubuntu.com/en/snappy/start/
をそのまんま実行しても動くには動くが・・

$ kvm -m 512 -redir :8090::80 -redir :8022::22 ubuntu-15.04-snappy-amd64-generic.img -curses
で起動はできけど、kvmのネットワークと繋がらない。kvmコマンドがよくわからないし面倒なので、libvirtへインストール方法で。

Snappyゲスト作成

https://help.ubuntu.com/community/KVM/CreateGuests
は参考にはなるが具体的なところがよくわからなくて結構悩んだ。

img => qcow2 変換 (必須じゃないけど、qcow2の方がサイズを小さくできるんで)

$ qemu-img convert -f raw -O qcow2 ubuntu-15.04-snappy-amd64-generic.img ubuntu-15.04-snappy-amd64-generic.qcow2

インストール

$ virt-install --name snappy --import --memory 512 --disk ubuntu-15.04-snappy-amd64-generic.qcow2

インストール後、勝手にコンソールが出てきて起動しちゃう。しかもキー入力が効かない。抜ける・・
でも、これでインストールは完了だ。

コンソール
$ virsh console snappy

Snappyは最初からsshd動いているんで、sshで入っても同じ。
ifconfigすると、ちゃんとKVMのゾーンのIPアドレスになっている。
外へpingしても、ちゃんと名前解決もできているし、外へも出られているようだ。

consoleから抜けるには、"Ctrl+5"とか"Ctrl+]"で抜けられる。

VNCから使ってみる

Snappyインスタンス内では、webdm/snappydが稼働しているので、見てみたい。
けれど、kvmコンテナ以外からのアクセスは出来ない。
そこで、KVMコンテナで、tightvncserverを動かして、そこに外からVNC接続して
そこからブラウザで
http://snappy-ip-address:4200
にアクセスすると、snappyアプリのダウンロードマネージャを操作できる。

仮想の仮想になっているんで、NATを2重にやれば外からのアクセスもできるはず。


左上に出ているのがSnappyゲストで疎いているwebdm画面で、SnappyStoreを覗いているところ。その横がSnappyのコンソール。左下がKVMのvirt-manager、で右下には愛用のEmacs上でbashを使っている状態。

Snappyを動かす環境として完全な状態か?といえば、そうじゃないと思う。
その都度、kvmやlxcの設定をやることになるんだろうなと思っている。それがまた楽し。

2015/10/06

RaspberryPiのモニタ出力をVNC経由で表示

前記事で、RaspberryPi2(ModelB)にRasbianをセットアップして、普通にRasbianデスクトップ(LXDE)をVNCで表示するところまでやりましたが、モニタ接続しないで描画デモを見るには?と調べてたら dispmanx_vnc に行き着きましたので、書いておきます。

何分RaspberryPiデビューが遅かったもんで、今こんなことをやっています。

目的は
・モニタレスで DEMO PROGRAMS にあるような描画デモを一通り動かしてみたい。
・遠隔地からVNC経由でRaspberriPiのディスプレイ出力を得たい。

最終的にはこんなイメージになります。
(スナップショットを載せないポリシーなのだけど、こればっかりは無いと辛いんで)


ということをできるようにするまでの流れ

dispmanx_vncをビルドするのに必要なライブラリのインストール
$ sudo apt-get install libvncserver-dev

dispmanx_vncをクローンしてビルド
$ git clone https://github.com/hanzelpeter/dispmanx_vnc.git
$ cd dispmanx_vnc
$ sh ./makeit

起動
$ sudo ./dispman_vncserver -r

すると、5900ポートでVNCサーバーが1つ立ちます。
後はどこからでもVNCでRaspberryPiのコンソール画面が転送されてきます。

本題はここまで

自分はRaspberryPi(Raspbian)自体のデスクトップに表示してみたかったので、RasberianにVncViewerインストールして、VNC内でVNCしてみました。

VncViewerインストール(vnc4viewerでもいいと思う)
$ sudo apt-get install tightvncviewer

自分の5900ポートをvncする。RaspberriPiのコンソールディスプレイそのものが現れる。
$ xtightvncviewer localhost:5900

デモをビルド
$ cd /opt/vc/src/hello_pi
$ ./rebuild.sh

どこかの端末からデモを1つ実行してみる。
$ cd hello_triangle
$ ./hello_triangle.bin


するとイメージのような状態になりました。15fps程度でキューブがくるくる回ります。

dispmanxって何だ?って調べてたら Raspberry Pi VideoCore APIs に概要説明がありました。
自分の理解としては、raspbianにプリインストールされている/opt/vc 自体がそれってこと。
dispmanxもvcの一部で、OpenGLESデモもビデオデバイス制御にvcライブラリを使っている。

dispmanx経由でディスプレイに描画されたイメージを直接キャプチャしてVNCストリームするツールがdispmanx_vncってことだね。

RaspberryPi用のXBMCやQtもこのVCライブラリを使っているらしいので、それらもVNC転送可能だということですね。

Raspberry Pi2 ModelB にlxcコンテナからraspbianインストール

気になっていたRaspberry Pi 2 Model B を購入しました。
https://www.raspberrypi.org
以前は教育目的じゃないと入手が困難だったけど今はアマゾンから普通に買えるのね。
同時にcocopar™ の3.5インチタッチパネル ディスプレイと定番の電子工作キットも購入。
色々遊んでみようと思う。

まずは、RaspbianをMicroSDに書き込まねばならない。
Ubuntuマシンとして使っているShuttleDS57UにSDCardソケットがあるんで、そこ使って書き込もうと思う。

RaspberryPi関係のことをやるためのlxcコンテナを作成して、そこから書き込むことにしよう。

マスターにしているコンテナをクローン
$ sudo lxc-clone master -n raspberry
$ ssh raspberry

自分の環境の場合、SDRAMカードデバイスは/dev/sdaになっている。一番最初のブロックデバイスなのだね。(メインのSSDストレージは/dev/sdb)
標準ではコンテナからはブロックデバイスへのアクセスはできないようになっている。

lxcコンテナから/dev/sdaへ書き込みできるようにする必要がある
http://forum.proxmox.com/threads/23256-LXC-Cannot-assign-a-block-device-to-container
を参考に

/var/lib/lxc/raspberry/config に以下の設定を追記
# block device
lxc.aa_profile = lxc-container-default-with-mounting
lxc.cgroup.devices.allow = b 8:* rwm
lxc.hook.autodev = /var/lib/lxc/raspberry/mount-hook.sh
mount-hook.sh は以下のような感じ
#!/bin/sh
mknod -m 666 ${LXC_ROOTFS_MOUNT}/dev/sda b 8 0
mknod -m 666 ${LXC_ROOTFS_MOUNT}/dev/sda1 b 8 1
”b”はブロックの意味かな。"8"とか"8 0"とかは、ホストで ls -l /dev/sda* とした時のデバイスのメジャーID、マイナーIDで、コンテナでも同じになるように。

起動時にフックが働いて、/dev/sdaと/dev/sda1が作成され、使えるようになる。

コンテナconfファイルの詳細は
https://linuxcontainers.org/ja/lxc/manpages/man5/lxc.container.conf.5.html

raspbianインストール
wgetでDownload ZIP からraspbian_jessie.zipをダウンロード。

unzipをインストールして、zipファイルを解凍
$ sudo apt-get install unzip
$ unzip raspbian_jessie.zip


日付-raspbian-jessie.imgファイルが出来上がる。

書き込み
$ sudo dd bs=4M if=/home/ubuntu/2015-09-24-raspbian-jessie.img of=/dev/sda

RaspberryPiにLANと焼いたMicroSDを差し込んで電源オン

IPアドレス調べるのがちょっと面倒
ホストから、
$ nmap 192.168.1.0/24
で、見慣れない奴を見つける。きっとそれがRaspberryPiだ。
早速 sshして、固定アドレスに変更。

設定をちょっとやっておこう

$ sudo raspi-config
Expand Filesystemを選ぶと、SDRAM全体が使えるようになる
Boot OptionでB1 Console
Internationalian OptionでTimezoneをAsia/Tokyo
Advanced OptionでB8 Serial Disable
ざっとこれくらい。

まずはVNCでもやって、様子を見てみる

RaspberryPiにVNCサーバー入れて起動
$ sudo apt-get install tightvncserver
$ tightvncserver
VNC接続パスワード設定。readOnly?はnと答える

raspberryPiのIP:5901へVNC接続するとデスクトップが出てくる。LXDEなのだね。

プログラムの教育用だけあって、開発環境はすでに色々入っているようですね。
特に、/opt/にはライブラリやらデモプログラムやらがどっさり。
これから、いじってみよう。

2015/09/30

lxcコンテナへのNAT設定もsystemdで

昨夜久しぶりにホストをリブートした。そしたらlxcコンテナへのNAT設定が消滅した。永続化してなかったので、当然です。そうかと軽く考えて

iptable設定永続化パッケージインストール
$ sudo apt-get install iptables-persistent

で、コンテナへのNAT設定を保存し、ホスト再起動してみたら、あらあら、
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  10.0.3.0/24         !10.0.3.0/24
MASQUERADE  all  --  10.0.3.0/24         !10.0.3.0/24
lxcbr0の設定がダブってしまうじゃありませんか。なるほど。lxcのネットワーク設定とiptables-persistentの設定の両方が効いてしまうからでしょうか。

Enable LXC neworking in Debian Jessie, Fedora 21 and others
には、/usr/lib/x86_64-linux-gnu/lxc/lxc-net ファイルに追加設定する方法があったり

Tips for LXC: Creation, Autostart, OpenVPN and Port Forwarding to Containersには、iptables-persistentでいいが、起動に問題があるのでちょっとゴニョゴニョするとか

それぞれのアプローチ方法は参考にはなるが、ちょっと好きになれない感じ。
自分なりの解決方法としては、iptables-persistentをやめて、前記事と同じsystemdを使って設定することにした。

ただ、今回はホストでの設定だし、ユーザモードで実行するものでもないので、普通のsystemdサービスユニット。それと追加変更がし易い形にはしようと思う。

サービスユニットファイル
~/.config/systemd/system/lxc-nat.service として作成。どこでもいいと思うが、ユーザモードの場合 ~/.config/systemd/user以下に置く流れに沿ってみた。内容は以下のとおり
[Unit]
Description=LXC NAT network setup
After=lxc-net.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/home/ubuntu/bin/lxc-nat start
ExecStop=/home/ubuntu/bin/lxc-nat stop

[Install]
WantedBy=multi-user.target
登録 (/etc/systemd/systemへシンボリックリンク作成)
$ sudo systemctl enable ~/.config/systemd/system/lxc-nat.service

自分でインストール先へ直接ln -sしてもいいかもしれないけど、systemctlにやってもらいます。

確認
$ sudo systemctl list-unit-files | grep lxc
lxc-nat.service                        enabled
lxc-net.service                        enabled
lxc.service                            enabled
lxcfs.service                          enabled

/home/ubuntu/bin/lxc-nat の中身の例
#!/bin/sh -

nat()
{
    iptables -t nat $1 PREROUTING -p tcp -d 192.168.1.10/32 --dport 80 -j DNAT --to 10.0.3.10:80
    iptables -t nat $1 PREROUTING -p tcp --dport 8000 -j DNAT --to 10.0.3.11:80
}

case "$1" in
    start)
        nat -A
    ;;
    stop)
        nat -D
    ;;
    *)
        echo "Usage: $0 {start|stop}"
        exit 2
esac
exit $?
nat()関数で-A/-D切り替えできるように(start/stop両方に書かないで済むように)。
後は、nat()内のiptables記述を増やすなり編集するなりするだけ。

例の場合の、10.0.3.10 のコンテナも自動起動するようにコンテナ設定ファイルに
lxc.start.auto = 1
としておくと尚良し。

2015/09/28

unicornとsidekiqをユーザレベルのsystemdで自動起動

lxcコンテナでRailsアプリである録画システムを動かすことに成功したので、永続化のためにコンテナの再起動時に自動起動するようにしたいわけです。

今までどおりなら、/etc/init.dに複雑な起動と停止のスクリプトを書くか、/etc/init.にupstart用のスクリプトファイルを置くのでしょうが、せっかく Ubuntu15.04 から systemd がデフォルトになったので、これを期にRailsアプリのデーモンをsystemdで扱えるようにしようと。

systemd/ユーザ
こちらを読むと、ユーザがログインした時に起動して、ログアウトで停止するようなサービスを定義できるらしい。更にそれをブート時に起動して、ユーザのログイン・ログアウトに依存しない永続化ができるらしい。これだぁ。

lxcコンテナでも、systemd --user デーモンがちゃんと稼働している。

今回デーモン起動したいサービスは、Railsのunicornとsidekiq。
これをsystemdのユーザサービスとして起動できるようにする。

systemdのサービスUnit定義ファイルを、ユーザのホームディレクトリの所定の場所に記述

SidekiqサービスUnitファイル .config/systemd/user/sidekiq.service

[Unit]
Description=Recman Sidekiq

[Service]
Type=forking
WorkingDirectory=%h/recman
ExecStart=/usr/local/bin/sidekiq -C ./config/sidekiq.yml -d

[Install]
WantedBy=default.target

UnicornサービスUnitファイル .config/systemd/user/unicorn.service

[Unit]
Description=Recman Unicorn

[Service]
Type=forking
WorkingDirectory=%h/recman
ExecStart=/usr/local/bin/unicorn_rails -D -c ./config/unicorn.rb -E development

[Install]
WantedBy=default.target

ExecStartは、デーモン起動したらすぐに終わるコマンドなので、Typeはforking。
Railsアプリのディレクトリに移ってから実行する必要があるので、WorkingDirectoryで指定する。
%hはユーザのホームディレクトリを示す変数。
インストール先は、default.target。systemd --userによって、defaut.targetユニットが予め存在するらしい。

とにかく、すごいシンプル!こんなでいいんだ。って感じ。

これらを登録して自動起動設定するまでの流れ
以降の操作はすべてユーザ権限で行える。systemctlに'--user'を付ける。

ユーザUnitファイルを読みこませる
$ systemctl --user daemon-reload

ちゃんと認識したか確認

$ systemctl --user list-unit-files

sidekiq.serviceとunicorn.serviceが、disableの状態でリストされる
ここまで行けば、手動での起動と停止ができるようになる

手動起動
$ systemctl --user start sidekiq
$ systemctl --user start unicorn

停止
$ systemctl --user stop sidekiq
$ systemctl --user stop unicorn

ExecStopを記述しなくても、停止させることが出来る。楽ちん。

自動起動するように有効化する

$ systemctl --user enable sidekiq
$ systemctl --user enable unicorn

再度確認してみた時の結果
ubuntu@recman:~$ systemctl --user list-unit-files
UNIT FILE            STATE   
sidekiq.service      enabled 
systemd-exit.service static  
unicorn.service      enabled 
basic.target         static  
bluetooth.target     static  
default.target       static  
exit.target          disabled
paths.target         static  
printer.target       static  
shutdown.target      static  
smartcard.target     static  
sockets.target       static  
sound.target         static  
timers.target        static

ubuntu@recman:~$ systemctl --user status unicorn
unicorn.service - Recman Unicorn
   Loaded: loaded (/home/ubuntu/.config/systemd/user/unicorn.service; enabled; vendor preset: enabled)
   Active: active (running) since 日 2015-09-27 17:59:14 JST; 15min ago
 Main PID: 1575 (ruby2.1)
   CGroup: /lxc/recman-1/user.slice/user-1000.slice/user@1000.service/unicorn.service

ubuntu@recman:~$ systemctl --user status sidekiq
sidekiq.service - Recman Sidekiq
   Loaded: loaded (/home/ubuntu/.config/systemd/user/sidekiq.service; enabled; vendor preset: enabled)
   Active: active (running) since 日 2015-09-27 17:59:12 JST; 15min ago
 Main PID: 1562 (ruby2.1)
   CGroup: /lxc/recman-1/user.slice/user-1000.slice/user@1000.service/sidekiq.service


永続化

ここまでだと、ログアウトと同時にユーザサービスは停止してしまうので、ブート起動して、ログイン/ログアウトに依存しない状態にする

$ sudo loginctl enable-linger

これでログアウトしても停止しなくなるし、lxcコンテナ起動時にサービスが開始される。
解除したい場合は、disable-lingerする。

$ sudo loginctl disable-linger

正直なところ、このloginctlとlingerがよく理解できていない。
enable-lingerするとセッションが壊れる危険性?があるということだが、どういうことなのか・・・

だけれど、とにかく、こうしたいっていう目的は達成である。

2015/09/26

lxcコンテナに録画システムを移設する

今年リニューアル構築した録画システムをlxcコンテナ上で動くようにしたいわけです。
なぜなら、面白そうだから。ですが、独立したクリーンな環境で動かせることが一番の魅力です。
色々実験していると環境がぐちゃぐちゃになっていきます。
いつ何を入れたのか、どこに何を設定したのか、次第にあやふやになってくる。etckeeperだけじゃキープできない。
「ちょっと試したい時にコンテナをクローンして試して失敗したら消す!」をしたい。

録画システム用コンテナ作成後に、以下ゴニョゴニョする

ビルド環境

$ sudo apt-get install build-essential
(for recfsusb2n)
$ sudo apt-get install libboost-filesystem-dev libboost-thread-dev -y
(for epgdump)
$ sudo apt-get install cmake -y
(for FFmpeg)
$ sudo apt-get install yasm libx264-dev libfaac-dev
$ sudo apt-get install pkg-config

ビルドしてインストール

epgdump、ffmpeg は、
$ make install
recfsusb2n は Makefileにinstall記述がないので直接
$ sudo cp ./recfsusb2n /usr/local/bin

もうここは専用環境なのでバンバン/usr/localとか自由に使う。

udevdが動いてないので

どうやらコンテナではudevdが動かないらしい。前回は'video'グループ権限でアクセスできるような
ルール設定ファイルを /dev/udev/rules.d に置いたりしたけど、udevdが動いてないので意味が無い。
っていうか、後で気づいたことだけど、ホスト側で設定しておくべきことだった。
ホストに/etc/udev/rules.d/89-tuner.rulesを作成。内容は以下な感じ。
# FSUSB2N
SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTRS{idVendor}=="0511", ATTRS{idProduct}=="0029", MODE="0664", GROUP="video"
これでブート時にチューナデバイスのグループが'video'になる。

USBデバイス スルー

コンテナ上の録画プログラム(recfsusb2n)がチューナーデバイスを扱えるように、
ホストに接続されているUSBデバイスをlxcコンテナからアクセスできるようにする必要がある。
# usb device
lxc.cgroup.devices.allow = c 189:* rwm
# mount
lxc.mount.entry=/dev/bus/usb/001 dev/bus/usb/001 none bind,optional,create=dir
上の記述を、コンテナのコンフィグファイルに追記して、コンテナ再起動する。
ホスト側と同じ状態でUSBデバイスをシェアした状態になる
コンテナから ls -l /dev/bus/usb/001/* すると
crw-rw-r-- 1 root root  189, 0  9月 23 20:13 /dev/bus/usb/001/001
crw-rw-r-- 1 root root  189, 1  9月 23 20:13 /dev/bus/usb/001/002
crw-rw-r-- 1 root root  189, 2  9月 23 20:13 /dev/bus/usb/001/003
crw-rw-r-- 1 root root  189, 3  9月 23 20:13 /dev/bus/usb/001/004
crw-rw-r-- 1 root video 189, 4  9月 25 17:50 /dev/bus/usb/001/005
crw-rw-r-- 1 root root  189, 5  9月 23 20:13 /dev/bus/usb/001/006

rvm入れなくても大丈夫かな
ubuntu14.04の時は、パッケージインストールされるrubyが1.9.3だったり、独立したruby環境にしたかったのでrvmを入れていたけど、ubuntu15.04のrubyは2.1.2で新し目だし、コンテナ自体が独立しているので、rvmではなく、普通にパッケージインストールしたものを使う。

gemインストール
録画システムはrailsで構築しているので、システムパッケージをまるっと持ってきて
(gitで退避しておいたので、git cloneして復活)そこで

$ bundle

で必要なgemパッケージをインストール
おっと、bundleも入ってなかったか。bundleが成功するまで、色々足りていないものを入れる。

(引っかかった順)
$ sudo gem install bundler
$ sudo apt-get install ruby-dev
$ sudo apt-get install zlib1g-dev
$ sudo apt-get install mysql-server libmysqlclient-dev

bundleでgemインストールが完了したら
$ sudo mysql_secure_installation&アクセス用のmysqlユーザを作って権限設定とかして、、

DB作成とマイグレーション

$ RAILS_ENV=production bundle exec rake db:create
$ RAILS_ENV=production bundle exec rake db:migrate
$ RAILS_ENV=development bundle exec rake db:create
$ RAILS_ENV=development bundle exec rake db:migrate

NATで外と接続(ホストで)
$ sudo iptables -t nat -A PREROUTING -p tcp --dport 3000 -j DNAT --to <コンテナIP>:3000

したら、コンテナ側のrailsアプリ上で
$ rails s -b 0.0.0.0

http://ホスト:3000 へアクセスして、コンテナ上のwebrickサーバーが反応すればテストOK。

recmanとしてちゃんと稼動させる
テストは良好だったので、サーバーのデーモン駆動周りを整える
$ sudo apt-get install redis-server nginx -y

unicornとsidekiqを稼動させて、nginxからunicornへproxyするように。

最後にwheneverでcrontab更新
$ bundle exec whenever --update-crontab

NAT設定して、外からアクセスできるように
$ sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to <コンテナIP>:80


思っていたよりすんなり移設できたが、気になる点としては、Ubuntu15.04からinitデーモンが、upstartではなく、systemdになったらしいこと。upstartも使えるけど、systemd対応の方が今風なのだと。unicornとsidekiqの自動起動をsystemd対応にしてみたい。

2015/09/25

lxc上でlxdを動かしたり・・・

シングルマシン1台でopenstackやらdevstackやらnovaのlxd版やら、色々試してみたものの動いたり動かなかったり、動いてもすごいリソース食いだったり(当たり前か)を一通り体感したので、原点に立ち戻った。

方針
* ホストはUbuntu15.04をクリーンインストールしたところに、openssh-serverと lxc だけ入れる
* lxdはlxcコンテナの中で動かす(仮想の仮想)
* 構築した録画システムは、lxcコンテナ上で稼働させる


インストール関係は過去の記事で書いたので、ここでは上のプランを実施する上で行ったことを書く。

lxcコンテナ作成と日本語化
ぼやぼやしているうちに、lxcもバージョンアップしたのか15.04だからか不明だけれど、テンプレートがスクリプトになったため、テンプレートからコンテナ作成するとものすごく遅くなった。日本語化も同時にやってくれるので面倒な無いけど、余計なものも入ってしまう。
ということでイメージダウンロードでコンテナ作成する。

$ sudo lxc-create -n test -t download -d ubuntu -r vivid -a amd64
前はこんな感じで出来た気がするのだけれど、-dが理解してくれない。仕方ないので

$ sudo lxc-create -t download  -n
で、対話式でやった。

このクラウドイメージは素の状態なので、日本語化とか、いくつか手を加える
#!/bin/bash
lxc-start -n $1
lxc-attach -n $1 -- adduser ubuntu
lxc-attach -n $1 -- passwd ubuntu
lxc-attach -n $1 -- apt-get install language-pack-ja -y
lxc-attach -n $1 -- apt-get install emacs -y
lxc-attach -n $1 -- update-locale LANG=ja_JP.UTF-8
lxc-attach -n $1 -- dpkg-reconfigure tzdata
lxc-stop -n $1
こんなスクリプトを書いてそのコンテナに対して実行すると、デフォルトのubuntuユーザと日本語化を一気にやる。といってもubuntuパスワード設定と最後のタイムゾーン設定は対話になる。

毎回 lxc-create するのは面倒なので、lxc-createしたものをマスターにして、実際使うコンテナはlxc-cloneして使うようにする。それから、マスターにはopenssh-serverは入れないようにする。
入れちゃうとクローンしてもちゃんと動かない。MACアドレスが変わるので認証コードも変える必要があるが、作りなおさせるのも面倒なので、最初から入れない。

sshサーバーはクローンして入れる
#!/bin/bash
lxc-clone $1 $2
lxc-start -n $2
lxc-attach -n $2 -- apt-get update
lxc-attach -n $2 -- apt-get install openssh-server -y
何度もやるのは面倒なので、クローンしてsshを入れるところまでのスクリプトを書いておく。

lxdを動かすlxcコンテナのNesting設定
lxcコンテナのコンフィグファイルに以下の設定を追記する
# Nesting
lxc.mount.auto = cgroup
lxc.aa_profile = lxc-container-default-with-nesting
これで、lxcコンテナにlxdをインストールして普通にlxdコンテナを稼働させることができるようになる。
自分の理解では、lxcならば仮想の仮想になってもCPUコストはほとんど変わらない。ただしネットワークがブリッジのブリッジになるのでその分通信パフォーマンスは落ちるか。
この辺は仮想ネットワーク構成を工夫すれば改善できるんじゃないかな。

何でこんなことするかというと、ホストに直接lxdを入れても動くが、いざlxcを使おうと思ってもlxd用のlxcになっているらしく思うように使えなかったため。dnsmasqも変。なので一番思い通りに動くlxcを親にした。いずれ lxdの方が一般的になってくるだろうから、その時に再考する。

コンテナ上でdevstack動くかな?
性懲りもなくやってみたが、当然というべきなのか動かなかった。
/lib/modulesがなかったり、ebtablesでエラーになったり。。コンテナ自体がカーネルを持っていないからだろうね。独立したカーネル上で動くKVMベースだったら動かせるのかなあ。

録画環境のコンテナ移設は次回。

2015/09/16

devstackにlxdコンテナ組み込み やってみたが・・


先の記事では、Shuttle DS57Uというスモール環境にopenstackを構築してみた話を書きました。

インスタンスをkvmからlxdに切り替えてみたいと思って色々試したのですが、残念ながら最後のインスタンス作成で失敗します。
手順は間違っていないと思っているので、手順自体は記録しておこうと思って書きました。

lxd は、lxcとopenstack/novaを繋ぐハイパバイザーってことらしい。
nova-compute-lxd をdevstackに組み込めば良いだけ。
Introduction to nova-compute-lxd が全てを教えてくれています。

Ubuntu 15.04 vividをクリーンインストールする(opensshだけ入れる)

$ sudo apt-get update
$ sudo apt-get install ntp git -y
$ sudo apt-get upgrade -y

ネットワークを固定IPにして、ipv6をディスエーブルする。お好みで。



パスワード要求無しでsudo出来るスーパーユーザを作る

$ sudo adduser stack
「stack ALL=(ALL) NOPASSWD:ALL」 を /etc/sudoers に追記

以降は全てstackユーザでの操作

$ su - stack

最初にnova-compute-lxdを/opt/stackにクローンする
別の場所にすると適応時に面倒なので、素直にここに置く

$ git clone https://github.com/lxc/nova-compute-lxd /opt/stack/nova-compute-lxd
nova-compute-lxd/contrib/devstack/README.first に書かれている通りにdevstackをクローン

$ git clone https://github.com/openstack-dev/devstack /opt/stack/devstack

devstackに対してlxdの設定スクリプトを実行する

$ cd /opt/stack/nova-compute-lxd
$ contrib/devstack/prepare_devstack.sh

すると、/opt/stack/devstackに対して、さくっと色々やってくれます。

/opt/stack/devstack/localrcファイルが作られているので、そこに自分の設定を追記します。
VIRT_DRIVER=lxd
export NON_STANDARD_REQS=1

SERVICE_TOKEN=shuttle01
ADMIN_PASSWORD=admin
MYSQL_PASSWORD=$ADMIN_PASSWORD
RABBIT_PASSWORD=$ADMIN_PASSWORD
SERVICE_PASSWORD=$ADMIN_PASSWORD

LOGFILE=$DEST/logs/stack.sh.log
LOGDAYS=2
な感じ。FIXED_RANGEやFLOATING_RANGEやら追加してもいいでしょう。自分はデフォルトでいいのでこんなもんです。

devstackのインストール&開始

$ cd /opt/stack/devstack
$ ./stack.sh

lxc、lxd、その他諸々、この中で一気にインストールとセットアップが行われます。
30分ほど待つと、めでたく lxdがインテグレーションされたopenstackが稼働します。

しかし残念ながら今のところ、コンテナ起動が成功しません。



クラウドイメージの準備・やってみたものの・・


標準で入っている cirros イメージはlxdコンテナとしては起動できません。

$ wget -O vivid-server-cloudimg-amd64-root.tar.gz https://cloud-images.ubuntu.com/vivi/current/vivid-server-cloudimg-amd64-root.tar.gz

$ source /opt/stack/devstack/openrc
$ glance image-create --name='lxc' --container-format=bare --disk-format=raw < vivid-server-cloudimg-amd64-root.tar.gz

これでイメージが取り込まれます。
が、これを起動したんですが、残念ながら起動できませんでした。

No valid host was found. There are not enough hosts available

になってしまう・・・

nova-compute-lxd/contrib/images/convert-ubuntu-cloud-images.sh というのがあったので

$ sudo ./convert-ubuntu-cloud-images.sh ~/vivid-server-cloudimg-amd64-root.tar.gz ~/vivid-server-cloudimg-amd64-root2.tar.gz
$ glance image-create --name='lxc2' --container-format=bare --disk-format=raw < vivid-server-cloudimg-amd64-root2.tar.gz

でトライしてみましたが、結果は変わらず。

ログを見ると、n-condのでエラーが出ている。
ソースを軽く追いかけると、selected_hostsがない。もしくは見つけられない?状態らしい。
インスタンスが必要とするvCPUの数と用意されている数を比較して足りない場合エラーっていう
処理のところらしいけど。

イメージが同行という以前のところでコケている感じ。まあ、もう少し待って再トライです。

openstackをプアーに体感してみる

lxc使ってみていたら、仮想環境に興味が湧いてしまいまして、色々やってみました。

今まで作ったものをバックアップして、Ubuntu 15.04 (vivid) でOS入れ直し。
このバージョンから仮想化向けの機能が追加されたので、良いかなと思って。

仕事では普通にVMWareを使ってVM構築したり、サーバー立ち上げたりしているけれど、クライアントユーザとして使っているだけだったので、実はあまり仮想化を知らなかったりする。
おもちゃにして使っている Shuttle DS75U を仮想ホストにしてプライベートクラウドしたら、どうなってしまうんだろ?しかも1台だけで、、、という個人的興味でのアプローチです。

lxcが動いているので、CPUの仮想化支援機能は問題ない。動くはずだ。

まずは OpenStack Installation Guide for Ubuntu 14.04 を読んで見るものの・・・
ほー。それぞれのコンポーネント構成は何とか理解するものの、 これを手動でセットアップするのはほぼ不可能に近い。

ということで Ubuntu OpenStack Installer を試みる

嬉しいことに、このインストーラーで入れるとコンピュートノードはlxcで作られる構成にしてくれるらしい。

本格的な構成とシングルマシン用のインストーラーということらしいので Single Installer Guide を頼りに実行してみた。こには
って書いてあるが、見なかったことにしてインストールしてみた。ものすごい時間を要したが特に途中何の問題もなくインストールが完了した。

が、しかし、それぞれのコンポーネント用のインスタンスが8つぐらい稼働しようとする。かなり頑張っていたが、3つ目くらいのインスタンス起動の途中から先に進まなくなった。
それぞれのインスタンスのメモリ設定が4GBとか6GBとかになっていて、実メモリ4GBしかないDS75Uでは太刀打ちできなかった様だ。。。

DevStack を使って入れる

DevStackっていうのは最初からあるインストーラーで、開発者のテスト環境構築にも使われているようで、タイニーな環境でも動こうような構成で入れてくれるらしい。

Ubuntu15.04では途中でこけていたが・・・
実はこの記事を書く前に、やった時にはUbuntu15.04ではインストーラースクリプトが途中でこけてしまう問題があった。DevStack自体が15.04に対応していなかったため、rabibitmqのスタートアップ周りで不具合があったのだ。
やってみると確かに、この部分で失敗した。 問題の箇所に関するバグIssue https://bugs.launchpad.net/devstack/+bug/1449056 を追っていたら、つい最近解決したコミットが!ということでmasterブランチをプルし直して
All-In-One Single Machineに書いてあるとおりにやったら問題なく15.04でも成功した。 やほ~。

ほぼデフォルト状態でのセットアップなので、次は、仮想ノードタイプをkvmからlxcにしたり、ネットワーク周りをいじったりしてみようと思う。

2015/08/15

lxcのlxdeをvncする

前回はlxcで仮想ubuntuを立ち上げてみるところまでやってみた。IPアドレスもdnsmasqで名前解決できるようになった。
そこで思い立ったのが、ホストの環境を汚さずに、デスクトップ環境のためのコンテナを作って、そこへ外部からVNC接続してみようかなと。飽きたらポイっとまるごと捨てればいいし。

lxcクローン
$ lxc-clone master ubu01

なにか構築したいなって時にすぐ始められるように、予め必要最低限のことを終わらせてあるubuntuコンテナをmasterとして作っておいて、lxc-cloneする。

起動
$ lxc-start -n ubu01 -d

sshログイン
$ ssh ubuntu@ubu01

lxdeインストール
$ sudo apt-get install lxde

ubuntu-desktopじゃなくてlxde。メモリもディスクも少ないShuttleではlxdeの方が小さくてよい。
それでも500MB位は消費してしまうが。

vncserver インストール
$ sudo apt-get install vnc4server

他にも色々VNCサーバーはあるようだけれど、扱いが簡単なvnc4server。

~/.vnc/xstartup作成 (っていうか、デフォルトをリンクしておくだけ)
$ mkdir ~/.vnc
$ ln -s /etc/X11/Xsession ~/.vnc/xstartup

vncserver起動(vncserverでもvnc4serverでもいっしょ)
$ vncserver :0

最初の起動時には、xauthのパスワード設定を要求してきます。適当に設定。
vncが 5900 で待ち受け状態になりました。


ホストの5900ポートを ubu01:5900 へルーティングする
(ホスト側で)
$ sudo iptables -t nat -A PREROUTING -p tcp --dport 5900 -j DNAT --to ubu01:5900

ホストとコンテナ間でのプライベートネットワークへ外部からアクセスできるようにするのに
一番お手軽な方法。

そしたら、外からホストの5900へVNC接続するとubu01:5900へルーティングされて、
めでたくlxdeのデスクトップへ到達します。

日本語化!

忘れてました。いつも端末ばっかりだったので、GUIには日本語化ってのがありました。
そもそも lxc で作った ubuntuコンテナってロケールがen_US.UTF-8だし・・・そこからか。

日本語フォント インストール(IPA入れとけばいいっしょ)
$ sudo apt-get install fonts-ipafont

とりあえずフォントさえ入れれば、lxdeで日本語が表示されます。

日本語パッケージインストール
$ sudo apt-get install language-pack-ja

ロケール設定
$ sudo update-locale LANG=ja_JP.UTF-8

/etc/default/localeに書き込まれる。が、今のシェルの環境変数はen_US.UTF-8のままです。

インプットメソッド
$ sudo apt-get install ibus-anthy

mozcの方が評判いいらしいが、自分にはぴんとこなかった。
ibusじゃなくてfcitxか?とも思って入れてみたけど、??な感じ。ibus-anthyで十分っす。

タイムゾーン変更(日本語化というより日本時間でってだけだけど)
$ sudo dpkg-reconfigure tzdata

CUIメニュー操作でAsiaからTokyo選ぶ

ここまでやったら、一度コンテナを再起動したほうが良い。環境変数とか変わるんで。
(ホストでリブート)
$ lxc-stop -n ubu01 -r

そしたら改めてコンテナ上で vncserver を起動して、そとからVNC接続すると lxdeデスクトップ自体も日本語表示になって、IMもibusで動いている状態に。


後処理
$ sudo iptables -t nat -D PREROUTING 1

遊び終わったら、DNAT設定を消しておこう。
コンテナも終わらせるか、vncserverを $ vncserver -kill :0 しておこう。
油断していると外から潜り込まれちゃうのでね。


2015/08/14

lxcのdnsmasqでコンテナのIPアドレス管理

lxc楽しくて、調子に乗って何個も作るとコンテナのIPアドレスでアクセスするのが面倒になってくる。
コンテナ側で固定IP設定するのもいいけど、lxc-cloneして使う場合、外側で管理しないと辛い。

ということで、コンテナ内はデフォルトのDHCP設定のままで、ホスト側で制御したい。

一番本家っぽいところを読む・・・
https://help.ubuntu.com/lts/serverguide/lxc.html#lxc-network

やっぱり、ありますね。方法が。

/etc/default/lxc-net ファイルの以下の記述があるんで、コメント外す
LXC_DHCP_CONFILE=/etc/lxc/dnsmasq.conf
LXC_DOMAIN="lxc" ←必要かなぁ。dnsmasqが使うかな。

/etc/lxc/dnsmasq.conf を作成して以下のように記述
dhcp-host=ubu01,10.0.3.11
dhcp-host=ubu02,10.0.3.12
dhcp-host=ubu03,10.0.3.13
もしくは
dhcp-hostsfile=/etc/lxc/dnsmasq-hosts.conf
と書いて、別ファイルにまとめることも出来るそうな。

$ sudo service lxc-net stop
$ sudo service lxc-net start
とかじゃなくてホスト再起動のほうがいいかも。

その後、コンテナを起動すると、指定アドレスになった。DHCP側で管理できるってことだね。
楽ちん。

ホスト側から名前でアクセスしたい

IP管理するんだったら全部 hosts ファイルに書くっていう手もあるが、それすら面倒くさい。
本家のドキュメントを読むと
server=/lxc/10.0.3.1 を /etc/dnsmasq.conf に書き加えろとか書いてあるんだけど、効果なし。
あちこちの記事でも書き加える場所とか説明がまちまちで、よくわからん。

DNS in Ubuntu + LXC が参考になりました。

/etc/resolvconf/resolv.conf.d/head とか /etc/network/interface当たりに
nameserver 10.0.3.1を加えればいいだけだった。

$ sudo resolveconf -u
更新して

$ ping ubu01
$ ping ubu01.lxc

が出来るようになった。これで、dhcp-hostでIP指定してなくても名前解決が出来るんで
更に楽ちん。


https://github.com/jeremiahsnapp/dev-lxc
これも面白そう。高度すぎ?

2015/08/13

lxcを入れてVM構築

LinuxContainers.org Infrastructure for container projects というLinuxカーネルのコンテナ技術を使って軽量VM環境を構築できるらしいものを最近知りました。

VMWare、Xen、KVM、もっと大きなのでは AWS とかが、VMの世界だと思っておりましたが、それら全部が openstack (cloud softwre) ファミリーだったのね。

ということで、軽量でメモリもディスクもあまり使わなそうな lxc でVMっぽいものをやってみた。
lxcの先には Docker とかいうものが控えているようだけれど、まずは基礎から。

lxcの勉強には LXCで学ぶコンテナ入門 を参考にさせてもらいました。

lxcインストール

$ sudo apt-get install lxc

すると
 /etc/lxc/ 設定
 /usr/share/lxc/ ディストーション設定やテンプレート
 /var/lib/lxc/ ルート権限のコンテナ格納場所
 /etc/init/lxc-**.conf 自動起動やらネットワーク設定やら
が作られて
 upstart-file-bridge
 upstart-socket-bridge
 upstart-udev-bridge
 cgmanager
 dnsmasq
がデーモンで動き始めました。

ifconfig すると、lxcbr0 っていうコンテナのネットワークをブリッジするインターフェースが追加されているのを確認。

root権限でコンテナ作って動かしてみる
$ sudo lxc-create -t ubuntu -n uc01

/var/lib/lxc/uc01にuc01という名前のubuntuが作られた。

起動!
$ sudo lxc-start -n uc01

動いた~。デフォルトのubuntuユーザでログインしてみた。ふむふむ。sshが出来る状態なのね。
えーと、コンソールから抜けるのは??? Ctrol+a qだそうな。やってみたけど抜けられぬ。。

停止(ホスト側で)
$ sudo lxc-stop -n uc01

コンソールから抜けられなかったので、外から殺しました。

デーモン起動して
$ sudo lxc-start -n uc01 -d

コンソール起動
$ sudo lxc-console -n uc01

おぉ? 今度は Ctrl+a q で抜けられた!まあいいか。次へ進む。
どうやらlxc-startをフォアグラウンドで起動した時のコンソールではダメで、lxc-consoleで起動したコンソールならば抜けられるようだ。

ネットワークタイプをmacvlanへ変更
デフォルトだと、仮想ネットワークが構築されて、それがホストに作られたブリッジインターフェース経由で外に出られるようになっていて、ホストから ssh 接続ができる状態。
これだと、外から uc01 へsshしたりは出来ない。

/var/lib/lxc/uc01/configを以下のように変更
 lxc.network.type = veth
 lxc.network.link = lxcbr0
 ↓
 lxc.network.type = macvlan
 lxc.network.link = em1 ← 普通はeth0とかになるところ。自分のShuttleではem1なのだ。

ホストの物理インターフェースに接続される。dhcpもいつものルータが担う。
ホストと同じように固定IP設定も普通にできる。

再起動
$ sudo lxc-stop -r -n uc01

確認
$ sudo lxc-ls -f
ふむ。IPアドレスが 192.168.1.31 とかになった。
コンテナから外に出れる。外部から コンテナ(uc01)へssh出来る。
ただし! ホストからはsshもpingも出来ない。
macvlanモード設定でコンテナ同士でのアクセスは出来るようになる。が、ホストはダメです。

コンテナ削除
$ sudo lxc-destroy -n uc01

なくなりました。
ここまでは全然何の問題もないっすね。あっけない。けど、裏ではものすごい技術の集合体です。

----

ユーザ権限でコンテナ作成

いくつかホスト側で設定が必要らしいが、ubuntu 14.04 だとそのほとんどは済んでいて、簡単な設定ファイルを用意するだけでよいようだ。

ユーザホームディレクトリ
$ mkdir .config/lxc
$ cp /etc/lxc/default.conf .config/lxc
$ echo "lxc.id_map = u 0 100000 65536" >> .conifig/lxc/default.conf
$ echo "lxc.id_map = g 0 100000 65536" >> .conifig/lxc/default.conf

lxc.id_mapへは、/etc/subuid と /etc/subgid に書かれているユーザのサブIDを設定する。
このsubuidとかはubuntuでは普通にユーザを作成すると作られている情報。

/etc/lxc/lxc-usernet ファイルに追記
$ sudo echo "user veth lxcbr0 10" >> /etc/lxc/lxc-usernet

"user"にvethでlxcbr0を使うコンテナを10個作れる。という特権を与える設定。
vethしかダメらしい。

作成(ユーザコンテナの場合、テンプレートには download しか選べない)
$ lxc-create -n ubu01 -t download

すると、どのディストリビューション入れる?って対話で聞いてくるので、それにそって入れても良いし、分かっているなら以下のようにオプション指定してもいい。

$ lxc-create -n ubu01 -t download -d ubuntu -r trusty -a amd64
ってな感じ。
ダウンロードイメージは毎日ビルドされているものをwgetで取得してくるようだ。

起動
$ lxc-start -n ubu01 -d

$ ps aux でプロセスを見てみると、UIDが100000位上のプロセスが10数個立つ。これがユーザコンテナのプロセスだろう。lxcはホストのカーネルプロセスの一部として動くので混ざるわけだ。

実はこの時ちゃんと起動しなくて、cgmanagerがエラーを出してクラッシュした。
何か設定が悪いのか?と調べてみたところ、lxcかcgmanagerのちょっとしたバグ?なのだろうか
ホストを再起動したら、直った。

起動してもログイン出来ないよ
ルート用テンプレートとは違い、sshdも入らないし、ubuntuユーザに初期パスワード設定されていないので、起動してもログイン出来ないのだ。これはセキュリティのための対処らしい。

ubuntuユーザにパスワード設定
$ lxc-attach -n ubu01 -- sudo passwd ubuntu

lxc-attachはコンテナにデフォルトユーザでログインしたつもりで外からコマンドを実行出来る。
親切にもダウンロードコンテナ作成するとその辺のガイドとかが表示される。

書いておきながら何だけど、デフォのubuntuユーザは触らずに、新たに作ったほうがいいと思う。

$ lxc-attach -n ubu01 -- sudo adduser user1
$ lxc-attach -n ubu01 -- sudo adduser user1 sudo

な感じで。

因みに、ユーザ起動コンテナのネットワークタイプはデフォルトの veth じゃないとだめらしい。
外に出れる。ホストからアクセスできる。が、外からはアクセス出来ないってこと。
セキュリティ的には良いが、外からアクセス出来ないとちょっとつまらないので、何とかしよう。

2015/08/05

svnrdump で uuid 取得できるね

今日は、実務の中でちょっとした発見ができたので、書き残しておくと思う。
世間にとっては既知なのかもしれないが・・・

日頃仕事でリポジトリ管理にオープン系の Svn と Git を扱っている。
Svnリポジトリの引っ越しやらバックアップやらで svnsync を使って同期状態にすることが多いが
「再配置(relocate)」が出来るようにするためには、リポジトリの UUID を同じにしておく必要がある。

svnsyncが出来るまでの基本手順
$ svnadmin create dest_repo_path
$ echo "#!/bin/sh" > dest_repo_path/hooks/pre-revprop-change
$ echo "exit 0" >> dest_repo_path/hooks/pre-revprop-change
$ chmod +x  dest_repo_path/hooks/pre-revprop-change
svnadminはローカルパスを指定する必要があるし、pre-revprop-changeファイル作成が必要
なので同期したいサーバー側で実行する必要があるかと思う。
実際の同期処理の、svnsync init と svnsync sync は、リモートから実行可能なので
アクセス可能なところから適当に。

本題はそこではなくて、再配置(relocate)可能な同期リポジトリにするためには先ほど書いたように
UUID を同じにしておく必要がある。

どうやって同期元リポジトリのUUIDを取得するか?

$ svnlook uuid src_repo_path とか
$ cat src_repo_path/db/uuid とか?

どちらにせよURLではなくファイルパス指定なので、

1.同期元リポジトリが存在するサーバー上で実行してコピペ?
2.NFSとかSambaとかsshfsとか使ってマウントしてuuidコピー?

手順のスクリプト化には厄介な話だ。自分はちょっと前まで実際に sshfs で元サーバーのリポジトリをマウントしてuuidコピーする手順をスクリプト化していた。苦肉の策。

すげー簡単な方法があった! やったーと思っているのは自分だけかも・・・
$ svnrdump dump -r 0 {src_repo_URL} | awk -e '/UUID/ { print $2 }' > uuid_file_path
これで取れる。しかも1ラインで。
svnrdump は、svndump をリモートから出来るようにしたコマンドなので、同期したい側から元リポジトリのURL指定でダンプが取れる。

ありがたいことに、どのリビジョンを取得してもダンプ情報の3行目に元のUUIDが記述されている。
UUIDを取り出したいだけなので、0番取得としている。Rev#0は属性情報だけなのでどんな
リポジトリでも一瞬でダンプが取れる。

取れさえすれば、直接uuidファイルに書き込んでもよし、svnadmin setuuid を使ってもよし。

ということで、チェックアウトせずにリモート操作で、しかも簡単にuuidを取得する方法でした。

2015/07/28

クエリレコード作成と番組自動予約

手動で予約~録画、視聴の流れはできたかな。次は自動予約だ。
前回はRedmineプラグインだったので制約が結構あったが、今回は自由がある。
その代わり面倒なところもある。

Queryモデル作成

$ rails g  scaffold query title:string start_time:time end_time:time query_type_id:integer category_id:integer sub_category_id:integer priority:integer

Queryレコード作成・変更・削除が必要なのでscaffoldで生成したコードを全部使用する。

query_type_idは、予約:1か検索:2を示す部分で、自動予約処理で使用する。
予約したいわけじゃなく「今日のゴールデンタイムのドラマは?」 を見たいときは検索タイプ。

start_timeとend_timeは、日時ではなく時間帯を指定したいので datetime ではなく time 型である。

priorityは将来的なもので、自動予約で時間がバッティングした場合、どちらを優先するのか?を設定しておくことで勝ち負けも自動でやらせようかという思惑。

models/query.rbの中身
# -*- coding: utf-8 -*-
class Query < ActiveRecord::Base
  belongs_to :category, -> { where cate_type: 0 }
  belongs_to :sub_category, -> { where cate_type: 1 }, class_name: 'Category'
  belongs_to :query_type

  def list
    programs = Program.all
    programs = programs.where(category_id: category_id) if category.present?
    programs = programs.where(category2_id: sub_category_id) if sub_category.present?
    programs = programs.where("title like :name", name: "%#{title}%") if title.present?

    # todo: 時間がセットされていないと判断する賢い方法はないのか?
    unless start_time.strftime("%H%M") + end_time.strftime("%H%M") == '00000000'
      programs = programs.where("TIME(start_at) >= TIME(?) and TIME(start_at) <= TIME(?)",
                                start_time, end_time)
    end
    return programs
  end

end

queryのlistメソッドで条件にマッチした番組リストを返す。条件が設定されているかどうかを値が入っているかどうかで判断したいけど、time型の場合が厄介。

f.time_selectでblankありにして、blank設定のままでも"0:00"になってしまう。nilにならない。

これをどうしたら良いの?という記事はいくつか見つかる。
http://stackoverflow.com/questions/14367705/time-select-blank-field-saves-a-default-time-when-form-is-submitted
とかで語られているソリューションがほとんど。だけど自分の肌に合わなかったので、
start_time.strftime("%H%M") + end_time.strftime("%H%M") == '00000000'
と、若干強引な方法で確認する羽目となった。

検索結果表示は、番組リスト(Program#index)で済ませる
内部処理はこんな↓
    @q = Program.where(video_id: nil).ransack(params[:q])
    if params[:query_id].present?
      @programs = Query.find_by(params[:query_id]).list
    else
      @programs = @q.result
    end
    @programs = @programs.order(:start_at).page(params[:page])
query_id付きで呼ばれた場合と普通の時で処理分け。
ransackも機能する必要があるので、@qの処理はどっちでもやる。

自動予約

繰り返し実行なのでActiveJobではなく、rake task にして whenever で回す。
タスクはすごく単純。 予約タイプのQueryレコードから条件にマッチする番組の予約をするだけ。
# -*- coding: utf-8 -*-
namespace :query do

  desc "自動予約"
  task :reserve => :environment do
    Query.where(query_type_id: 1).each do |q|
      q.list.where(video_id: nil).where('start_at > ?',Time.now+60).each do |program|
        program.reserve
      end
    end
  end

end

予約済みや、今より過去の番組を予約しないように、若干の条件付けを施す程度。
これだけで結構ちゃんと機能している。
以前に書いた処理に比べれば大分シンプルにまとまったような気がする。

これで全体機能は一段落ですが、実際に運用し始めるとまた色々問題が出るんですよねぇ。

2015/07/25

予約レコード周りのその他こまごましたところ

大筋の機能は何とかイメージ通りに実装できたのですが、細かいところでも知らないがゆえに
引っかかりながらの実装となった点をまとめておく。

flashメッセージ表示

予約成功!、予約失敗などを表示するのをflashメッセージを使ってやろうとしたんだけれど、
bootstrapの場合、Rails標準のflashタイプじゃなくてbootstrap後に変換が必要なことを知らず
ちょっとはまってしまった。

通常 redirect_to うにゃうにゃ, notice: "成功!" ってやると思うんだけれど
この"notice"をbootstrap的にはclass "alert alert-success"とかに変換しないと出ない。
その辺りの記事は沢山あった。よくあるパターンとしては
notice => alert-info、alert => alert-warning、error => alert-danger とかに対応させている。
この通りやるのだが、メッセージが表示されない。もしくは無色で表示される。
違う。バックが青とか赤で囲われて欲しいのだよ。結局自分の場合、

notice => alert-success、alert => alert-danger とした。
バックカラーが、successがグリーン、dangerが赤で表示されるから。それ以外は白?だった。
それに、redirect_toのオプションで付けられるのは、notice、alert、flash => {ゴニョゴニョ} の3つ。
noticeとalertだけなら簡単だし。

予約時間の重複チェック

チューナー1つしか無いので、予約しようとした時間帯に引っかかる予約済みレコード確認をして、引っかかっしまう場合はエラーにしたいわけです。
検索としては、既存レコードの開始時間がその時間帯内である もしくは 終了時間がその時間帯内にある場合は・・・です。ということは ”OR” でつなげるわけです。
Program.where(ゴニョゴニョ)で一気に書こうとするとSQL文をなが~く書かないとならない。
嫌なので、"OR"はRubyでやることで全体コードをシンプルにした。

video_on = Program.where.not(video_id: nil)
start_on = video_on.where(start_at: (@program.start_at..@program.end_at))
end_on = video_on.where(end_at: (@program.start_at..@program.end_at))
if start_on.present? or end_on.present?
  redirect_to program_url, alert: 'Reserved violation another videos'
return
end
こんな感じ。BETWEEN生成はwhereに任せ、面倒なORはRubyで。
連続しているものは許したいので実際にはstat,endは微妙に調整する。

録画ジョブ開始タイミングは番組時間の30秒前

ActiveJobのperformが開始されて実際の録画を開始するまでには結構時間がかかる。
なのでJenkins処理でやっていた時のように、少し早めにスタートさせて sleep でタイミング調整
をするようにしている。
recfsusb2n自体にも--waitで待つ機能があるようだけど正確に働かない感じだったので
スクリプト内のsleepで待つようにした。

録画時間は15秒前終了

重なってはいないけど録画時間が連続している場合、多少のギャップを作ってチューナー
を空けておかないと録画開始時にデバイスエラーになってしまうので、若干短めにしておく。


とまあ、細かいところだけれど、録画失敗してがっかりとならないようにやっておかないとね。
で実際の録画ジョブコードは以下のとおり。
  def perform(video)
    if video
      # write path
      path = video.program.start_at.strftime("%Y%m")
      fdir = "#{VIDEO_ROOT}/#{path}"
      Dir.mkdir(fdir, 0777) unless Dir.exist?(fdir)

      fname = "#{path}/#{video.id}"
      fpath = "#{VIDEO_ROOT}/#{fname}"

      # recording
      channel = video.program.channel.ch
      duration = video.program.duration - 15
      wait = (video.program.start_at - Time.now).to_i

      sleep wait if wait>0

      p "------recording start: #{fname}"
      begin
        `tvoff`
        # recode
        `recfsusb2n -b -i hd #{channel} #{duration} #{fpath}.ts`
        `tvon`
        # encode
        p `ffmpeg -loglevel 8 -y -i #{fpath}.ts -c:v libx264 -c:a libfaac -preset superfast -f\
 mp4 -threads 2 -b:v 2000k #{fpath}.mp4`
        # poster
        p `ffmpeg -loglevel 8 -i #{fpath}.mp4 -ss 12 -vframes 1 -an -s 320x180 #{fpath}.jpg`
        # successed
        video.update_attributes(status_id: 2, filename: "#{fname}") # successfully.
        File.delete("#{fpath}.ts")

      rescue => e
        p e.message
        video.update_attributes(status_id: 3) # recording job failed.
      end
      p "------recording end: #{fpath}"
    end
  end

録画の前後でtvon、tvoffっていうのを実行するようになっていますが、これは自分が適当に
作ったシェルスクリプトでして、以下の様なシロモノです。
tvon
#!/bin/bash
recfsusb2n -b -i hd -H 8888 1>/dev/null 2>/dev/null
tvoff
#!/bin/bash
PID=`pidof recfsusb2n`
if test "$PID" != "" ; then
    kill -9 $PID
fi
録画していない時はリアルタイム視聴できる状態にスタンバイさせておこうというものです。
朝方とか結構重宝します。家族食事中は子供にTVを見せないポリシーなもんで。

2015/07/23

録画予約をActiveJob with sidekiq で

前回の記事で、録画ジョブをActiveJobでやる話を書きました。
実際にやってみて、これまた色々ハマったので今回はそのへんの話を書きたいと思います。

要件
1.EPG情報から直近7日間中の何時何分に実行という予約ができること。
その間、システムリブートしても予約が消滅しないこと。キューの永続化。
2.ジョブが失敗してもリトライしない。スケジュールした時間に意味あるので。
4.予約したものを途中キャンセルできること。


RecordJob という ActiveJob::Baseの派生クラスを作成
$ rails g job record
すると、app/jobs/record_job.rbファイルが作られます。最初は以下の様な感じ。
class RecordJob < ActiveJob::Base
  queue_as :default

  def perform(*args)
    # Do something later
  end
end
このperform関数内に録画処理をずらずらと書いておくわけですが、その内容とは関係ない周辺処理でいくつかハマってしまいました。

1.指定日時分に実行するジョブ登録と実行(perform)

登録自体は簡単です。
  # 予約
  def reserve
    start = self.program.start_at - 30
    job = RecordJob.set(wait_until: start).perform_later(self)
    update_attribute(:job_id, job.job_id)
  end
このなメソッドを、ビデオレコードモデル models/video.rb に書いておいて、予約時に video.reserveを呼ぶだけです。
RecordJob.set(wait_until: start).perform_later(self) がActiveJobを通してSidekiqにstartで指定した日時分に実行するスケジュールが登録されます。redisにスケジュールは永続化するので、途中システムリブートしても大丈夫。

なぜ perform_later(video.id)ではなく、perform_later(self) なのか?

ネット記事を読みながらやると、オブジェクト渡しはシリアライズが入ってよろしくないからID渡しをするのだ的な内容が多かったので、最初は自分もそうしてみました。
 ところが!
最初のperform実行ではちゃんとvideo.idが渡って滞り無く処理されるんですが、不思議なことに2回めのジョブ(もちろんIDは異なる)でも最初のIDでperformが実行されてしまいました。
perform(video_id)で受けるのをやめてみて perform(*args) で受けてみても結果同じでした。

↑後で気づいたんだけど、whereではなくfind_byを使ったためと思われる。perform(id)ができないというわけではないようだ。

その後知りましたが、現在のActiveJobのperformへはオブジェクト渡しは悪い方法ではないらしい。シリアライズ負荷がどうのという懸念は解消しているらしい。

ただオブジェクト渡しだと、ジョブ実行前にそのオブジェクトレコードが削除された場合は事前に調べることが出来ず、例外エラーとなってしまう。これはちゃんとレコード削除とジョブキャンセルを同期させれば問題ないか。

2.失敗してもリトライしないように

ネット上は色々なソリューションがあるようです。

ActiveJobオプションをジョブクラスに記述する。
job_options retry: false

Sidekiqオプションを記述する。
sidekiq_options :retry => false

ふむふむ? どっちも効きませんけど・・・
結局自分は、以下のコードを、initializers/sidekiq.rb に加えました。
Sidekiq.configure_server do |config|
   config.server_middleware do |chain|
     chain.add Sidekiq::Middleware::Server::RetryJobs, :max_retries => 0
   end
end

3.SidekiqスケジュールをRailsアプリからキャンセルする

放っておいてもジョブ失敗するだけでシステムが死ぬわけじゃないんで支障はない
のだけれど、ログが汚くなるし、sidekiq_webで見た時にわけわからなくなるんで、
予約キャンセルでジョブスケジュールも削除したいわけです。

探してみましたが、ActiveJobからキュー状態を調べたり削除したりすることが出来ない?

perform_laterが返すJobのjob_idは、SidekiqのjobIDとは異なる? むむぅ。調べた結果、
Sidekiqスケジュールのアーギュメント内にActiveJobのjob_idが記録されている。
ということで以下のコードをレコード削除メッソド内に記述することで解決。
jobs = Sidekiq::ScheduledSet.new.select do |job|
  job.args[0]['job_id'] == @video.job_id
end
jobs.each(&:delete)
冒頭のreserveメソッドでActiveJobのjob_idをレコード上に覚えておくのは、このため。
以上が、やりたいこと以外のやらなくてはならなかったこと。結構苦労した。

2015/07/19

録画処理をActiveJobで

実際の録画ジョブをどこでどうやって動かすか?

過去のアプローチとしては、Linuxの'at'コマンドでOS的なジョブ登録で回していた。
'at'は、お手軽でいいんだけれど、ジョブ管理が結構面倒だった。

その後、Jenkinsでジョブ管理するようにした。これはこれで良かったが、Jenkins自体がメモリとCPU食いなので、1台で全部やらせようとするとキャパ的にきつかった。

今回完全リニューアルということで、調べなおしてみたところ、Rails4以降では、ActiveJob というものが標準化したらしい。ならば、使ってみようじゃないですかと。

ActiveJobはただのフロントAPIであって、実際のジョブは別のジョブキューイングシステムに任せるようだ。その辺の解説は、http://blog.chopschips.net/blog/2015/02/26/active-job/がいいです。

Sucker Punch

バックエンドに簡単そうな、Sucker Punch を使ってやってみることにした。作者の記事もなかなか面白いです → Why I Wrote the Sucker Punch Gem

いつものようにGemfileに、gem 'sucker_punch' を追記して、$ bundle しておく。
config/initializers/sucker_punch.rbファイル作成して
Rails.application.configure do
  config.active_job.queue_adapter = :sucker_punch
end
と書いて、ActiveJobのバックエンドとしてSuckerPunchを使うようにして準備完了。
簡単にできるはずが・・・

実は色々ハマりました
SuckerPunchは実は Celluloid のラッパーに等しい。このCelluloidの挙動がむずい。

1.予約録画モデルのVideoモデルがジョブキューを持てばいいんじゃないか?
と、いきなりSuckerPunchを直接使ってしまおうと思った。そこで、Videoモデルに
include SuckerPunch::Job
を入れてみたら、Videoモデル全体がCelluloidでラップされてしまい、.createとか、.first_or_createとかが全然動かなくなってしまった。newしてsaveすれば?と書き方を変えて粘ってもみたが、 全く太刀打ち出来無かった・・・

気持ちを新たに、rails g job RecordJob で、RecordJobというActiveJobを作ってからのこと・・・

2.perform_laterと10秒制限?
RecordJob.perform_laterでキュー登録してみた。performメソッド内で録画タイミングを図るためにsleepをさせていた。perform_laterからperformはコールされるが、perform内は10秒以内の処理じゃないと強制的にアボートしてしまうようだ。はぁ?って感じ。

 RecordJob.set(wait_until: video.program.start_at).perform_later(video)
気持ちとしては、上記の1行で予約時にキュー登録したいのだが、試しにやってみたら、wait_untilはSuckerPuchでは実装されていなかった・・・

残念ながら、SuckerPunchは目的には合致しなかったようだ。撃沈。

やっぱり普通にSidekiqか

sidekiqをバックエンドに使用することにしたので redis 入れる。


$ apt-get install redis-server

Gemfileにgem 'sidekiq'を追加して、$ bundle実行。
sidekiqは、$ sidekiq で直接動かしてテスト実行。

今度は、
 RecordJob.set(wait_until: 録画開始時間).perform_later(video)
が普通に動いた。
後は、sidekiqをサーバ自動起動化して、タスク環境はよしとしよう。

lm-sensors 入れておく

Ubuntu14.04 on DS57Uに色々構築中だが、ファンレスPCは自分自身初めてなので、これからの猛暑を乗りきれるのか?と不安で。

で、そういえば以前も確認したりしてたなぁと思い出し、lm-sensors入れておこうと思います。

$ sudo apt-get install lm-sensors
$ sudo sensors-detect

の時の表示ログ↓
# sensors-detect revision 6170 (2013-05-20 21:25:22 +0200)
# System: Shuttle Inc. DS57U [V1.0]
# Board: Shuttle Inc. FS57U

This program will help you determine which kernel modules you need
to load to use lm_sensors most effectively. It is generally safe
and recommended to accept the default answers to all questions,
unless you know what you're doing.

Some south bridges, CPUs or memory controllers contain embedded sensors.
Do you want to scan for them? This is totally safe. (YES/no):
Module cpuid loaded successfully.
Silicon Integrated Systems SIS5595...                       No
VIA VT82C686 Integrated Sensors...                          No
VIA VT8231 Integrated Sensors...                            No
AMD K8 thermal sensors...                                   No
AMD Family 10h thermal sensors...                           No
AMD Family 11h thermal sensors...                           No
AMD Family 12h and 14h thermal sensors...                   No
AMD Family 15h thermal sensors...                           No
AMD Family 15h power sensors...                             No
AMD Family 16h power sensors...                             No
Intel digital thermal sensor...                             Success!
    (driver `coretemp')
Intel AMB FB-DIMM thermal sensor...                         No
VIA C7 thermal sensor...                                    No
VIA Nano thermal sensor...                                  No

Some Super I/O chips contain embedded sensors. We have to write to
standard I/O ports to probe them. This is usually safe.
Do you want to scan for Super I/O sensors? (YES/no):
Probing for Super-I/O at 0x2e/0x2f
Trying family `National Semiconductor/ITE'...               No
Trying family `SMSC'...                                     No
Trying family `VIA/Winbond/Nuvoton/Fintek'...               No
Trying family `ITE'...                                      Yes
Found `ITE IT8728F Super IO Sensors'                        Success!
    (address 0xa40, driver `it87')
Probing for Super-I/O at 0x4e/0x4f
Trying family `National Semiconductor/ITE'...               No
Trying family `SMSC'...                                     No
Trying family `VIA/Winbond/Nuvoton/Fintek'...               No
Trying family `ITE'...                                      No

Some systems (mainly servers) implement IPMI, a set of common interfaces
through which system health data may be retrieved, amongst other things.
We first try to get the information from SMBIOS. If we don't find it
there, we have to read from arbitrary I/O ports to probe for such
interfaces. This is normally safe. Do you want to scan for IPMI
interfaces? (YES/no):
Probing for `IPMI BMC KCS' at 0xca0...                      No
Probing for `IPMI BMC SMIC' at 0xca8...                     No

Some hardware monitoring chips are accessible through the ISA I/O ports.
We have to write to arbitrary I/O ports to probe them. This is usually
safe though. Yes, you do have ISA I/O ports even if you do not have any
ISA slots! Do you want to scan the ISA I/O ports? (yes/NO):

Lastly, we can probe the I2C/SMBus adapters for connected hardware
monitoring devices. This is the most risky part, and while it works
reasonably well on most systems, it has been reported to cause trouble
on some systems.
Do you want to probe the I2C/SMBus adapters now? (YES/no):
Found unknown SMBus adapter 8086:9ca2 at 0000:00:1f.3.
Sorry, no supported PCI bus adapters found.
Module i2c-dev loaded successfully.

Next adapter: i915 gmbus ssc (i2c-0)
Do you want to scan it? (yes/NO/selectively):

Next adapter: i915 gmbus vga (i2c-1)
Do you want to scan it? (yes/NO/selectively):

Next adapter: i915 gmbus panel (i2c-2)
Do you want to scan it? (yes/NO/selectively):

Next adapter: i915 gmbus dpc (i2c-3)
Do you want to scan it? (yes/NO/selectively):

Next adapter: i915 gmbus dpb (i2c-4)
Do you want to scan it? (yes/NO/selectively):

Next adapter: i915 gmbus dpd (i2c-5)
Do you want to scan it? (yes/NO/selectively):

Next adapter: DPDDC-B (i2c-6)
Do you want to scan it? (yes/NO/selectively):

Now follows a summary of the probes I have just done.
Just press ENTER to continue:

Driver `it87':
  * ISA bus, address 0xa40
    Chip `ITE IT8728F Super IO Sensors' (confidence: 9)

Driver `coretemp':
  * Chip `Intel digital thermal sensor' (confidence: 9)

To load everything that is needed, add this to /etc/modules:
#----cut here----
# Chip drivers
coretemp
it87
#----cut here----
If you have some drivers built into your kernel, the list above will
contain too many modules. Skip the appropriate ones!

Do you want to add these lines automatically to /etc/modules? (yes/NO)yes
Successful!

Monitoring programs won't work until the needed modules are
loaded. You may want to run 'service kmod start'
to load them.

Unloading i2c-dev... OK
Unloading cpuid... OK



取得可能な情報は、coretemp(CPU温度)とSuper IOでした。Super I/Oとは、
https://en.wikipedia.org/wiki/Super_I/O によると低速な外部デバイスポートを接続するコントローラの部分でしょうか。とりあえず欲しいのはCPU温度だけなので、coretempさえ取れればいいや。
因みに、/etc/modulesにセンサードライバを記述するのだが、それもsensors-detectさんにやってもらいました。

$ sensors

正午ごろの結果↓
acpitz-virtual-0
Adapter: Virtual device
temp1:        +27.8°C  (crit = +105.0°C)
temp2:        +29.8°C  (crit = +105.0°C)

coretemp-isa-0000
Adapter: ISA adapter
Physical id 0:  +44.0°C  (high = +105.0°C, crit = +105.0°C)
Core 0:         +42.0°C  (high = +105.0°C, crit = +105.0°C)
Core 1:         +43.0°C  (high = +105.0°C, crit = +105.0°C)


ふむふむ。思ったほど高温でもないような。



CPUの動作周波数は?

$ grep MHz /proc/cpuinfo
 cpu MHz         : 806.718
 cpu MHz         : 799.746
DS57UのCPUは、Celeron 3205U(1.5GHz) なので、こんなもんでしょうねぇ。


無駄記事だったかもしれないけど、これから猛暑に突入なのでファンレスPCの温度管理は時々しておいたほうがいいし、最近この手の記事が見当たらないので補填の意味で。

video(録画)テーブルの設計と実装

番組テーブルの実装とリスト表示、検索、ソート等の実装がおおかた落ち着いたので、次はいよいよ予約、録画、視聴のためのレコード設計に入ります。

予約・録画レコードを、Videosとするとして、それがどのような状態かを表す必要がありますね。
予約・録画・失敗とかです。
それを表現するためのマスターレコードをまずは作っておきましょう。

ステータスレコード
$ rails g model status name:string
で、ちょっと乱暴ですが、レコードID決め打ちで意味をもたせます。単純にこれだけなので、seedで入れてしまいます。
db/seeds.rb
 Status.create(name: '予約')
 Status.create(name: '録画')
 Status.create(name: 'キャンセル')
 Status.create(name: '失敗')

$ rake db:migrate
$ rake db:seed
これでステータスレコードは完成。

ビデオレコード
$ rails g scaffold video status_id:integer program_id:integer filename:string
後で全部書き換えることになると思うけれど、scaffoldで初期状態のviews/controllersを作ってもらいます。
それに、後で幾つかレコードデータも追加していくことになるでしょう。

status_idが、先ほどのStatusへのリンク用
program_idが、番組テール部へのリンク用
filenameが、録画したファイル名。ファイルパスとか何とかは後で考える。

論理削除を使うかやめるか?
Programレコードの方だが、過去レコードはどんどん削除するつもりでいるが、Videoレコードには番組情報を持たずに、program_idでリンクを貼るつもりでいる。ならば過去レコードであっても削除されては困るわけで。
じゃあ、deleted_atを付けて、「paranoia」とかで論理削除を組み込むか?組み込むなら、
Gemfileに
 gem "paranoia", "~> 2.0"
を追加(rails 4.xは、v2.xを使うらしい)して、models/programs.rbに
 acts_as_paranoid
と書けば組み込まれるので簡単ではあるが・・・

やっぱりやめて、video_idをProgramベーブルに追加する方向で。
$ rails g AddVideoIdToProgram video_id:integer:index ←面倒になってきたのでindexも一緒に。

このvideo_idがNULLじゃなければ消さないようにする。今回の場合はこの方がシンプルで分かりやすいかなと。

確認
$ rails s -b 0.0.0.0
http://shuttle:3000/videos へアクセス。
おや? そうでした、まだ、bootstrapテーマをかぶせてませんでした。
$ rails g bootstrap:themed Videos
はい。いい感じ。
これにProgramsと同じように検索とソートを加えて見た目実装は完了。

次は実際の「予約・録画」ですか。ここまでほとんど自分でコードをガリガリ書くこと無く出来てしまいましたが、さすがにここからはコードを書かないとならんですねぇ。

ransackで検索とソートを組み込み

ransackを使って、番組リストの検索とソートをさくっと組み込みます。

Gemfileにgem 'ransack'書いて、bundle install。準備完了。

app/controllers/programs.rbのindex部は

に書き換え。searchとresultがransackの処理。
# @programs = Program.page(params[:page]) を下の2行に書き換え。
@q = Program.search(params[:q])
@programs = @q.result.page(params[:page])
controllerはこれだけで終わり。

ソートはテーブルヘッダをsort_linkで書き換える。
      <th><%= sort_link(@q, :channel_name, model_class.human_attribute_name(:channel_id)) %></th>
な風に。

views/programs/index.html.erb内の検索フォーム:

      <%= search_form_for @q do |f| %>
      

<%# include_blank:  でブランク行含む。文字列にすることも出来る%>

 <%= f.collection_select :channel_name_cont, Channel.all, :name, :name, include_blank: model_c\
lass.human_attribute_name(:channel_id) %>

 <%#= f.collection_select :channel_id_cont, Channel.all, :id, :name, include_blank: true %>

      
<%# multiple: true で複数選択 %>
<%= f.collection_select :category_name_cont, Category.where(cate_type:0).order(:name), :name,\
 :name, include_blank: model_class.human_attribute_name(:category_id), multiple: true %>
<%# multiple: true で複数選択 %>
<%= f.collection_select :category2_name_cont, Category.where(cate_type:1).order(:name), :name\
, :name, include_blank: model_class.human_attribute_name(:category2_id), multiple: true %>
<%# _gtでそれ以上という検索になる %>
<%= f.search_field :duration_gt, size: 5, placeholder: model_class.human_attribute_name(:dura\
tion) %>
<%= f.search_field :start_at_date_equals, size: 10, placeholder: model_class.human_attribute_\
name(:start_at) %>
<%= f.search_field :end_at_date_equals, size: 10, placeholder: model_class.human_attribute_na\
me(:end_at) %>
<%= f.search_field :title_cont, placeholder: model_class.human_attribute_name(:title) %>
<%= f.submit %>
<% end %>
channelは、collection_selectを使ってプルダウンメニュー型に
categoryは、Categoryレコード内に2タイプのカテゴリを混ぜているので、allではなくwhereでフィルタしています。
duration_gtですが、_gtを付けると「以上」を表現できます。
start_at_date_equalsの_date_equalsは、入力した文字列をDateに変換させる拡張をしています。

_date_equals拡張:
models/programs.rbに以下を加える

  ransacker :start_at do
    Arel.sql('date(start_at)')
  end
  ransacker :end_at do
    Arel.sql('date(end_at)')
  end
config/initializers/ransack.rbファイルを作成して以下のとおりとする

Ransack.configure do |config|
  # search_fieldで_date_equalsの内部処理                                                              
  config.add_predicate 'date_equals',
  arel_predicate: 'eq',
  formatter: proc {|v|
    begin
      v.to_date
    rescue
    end
    },
  validator: proc {|v| v.present? },
  type: :string
end
詳しくはこちら、
https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers
に書いてあるとおりです。

turbolinks問題
えーと、実はよくわかってはいないのですが、Rails4では、turbolinksとやらがデフォルトでつくようになったそうで、これが色んな所でよくわからない挙動というか挙動しない現象がありました。

ransackのsort_linkでソートした後にsearch_form_forのsubmitが効かなくなる!
link_to で、programs_path へ飛ばした後のsearch_form_forのsubmitが効かなくなる!
いろいろ調べて、jquery-turbolinksを入れてみましたが結果変わらず。
link_toに "data: {no_turbolink: 1}"を付けてみたところ機能はしましたが、sort_linkにこのオプションを付ける方法が分からず断念。

結局、turbolinksを外すことで解決となりました。

ルック&フィールを一気に片付ける

kaminari でページ、bootstrapで全体フィール、ransackで検索とソート を作っておしまいにする

bootstrapとkaminariは以下の記事を参考にして、さくさくっと。
http://ruby-rails.hatenadiary.com/entry/20140801/1406818800
http://ruby-rails.hatenadiary.com/entry/20141113/1415874683#kaminari-install-with-bootstrap

Gemfileに以下を追加して、bundle install !
gem 'kaminari'
gem 'less-rails'
gem 'twitter-bootstrap-rails'
gem 'ransack'

kaminari
$ rails g kaminari:config

コントローラファイル編集
@programs = Program.all

@programs = Program.page(params[:page])

ビューファイル編集
<%= paginate @programs %>

を最後に加えるだけ

bootstrap
$ rails g bootstrap:install
$ rails g bootstrap:themed Programs
$ rails g kaminari:views bootstrap3 ← rail3だと適応できなかったけど、rail4だと大丈夫だ。

これでProgramビューを一気にいい感じにしてくれる。


ここまでやったら、viewsを少し整えます。

models/programs.rb

  belongs_to :channel
  belongs_to :category
と書いておくと、あら不思議、channel_idとcategory_idから勝手にjoinした状態でアクセスできるようになります。

views/programs/index.html.erb
@program.channel_idを、@program.channel.nameに書き換えれば、その番組のchannel名を簡単に表示される。

scaffoldやbootstrapで自動作成したリストから不要な表示を削除してすっきりさせる。
showページへは、idからのリンクではなく、titleからのリンクにしたいので
id表示を削除して、title部分を

<td><%= link_to program.title, program_path(program) %></td>

のようにします。
detailや、録画予約ボタンはshowページで後ほど。

config/initializers/time_formats.rb ファイルを作成し、以下のように

Time::DATE_FORMATS[:default] = '%Y/%m/%d %H:%M'
class Integer
    def time_duration
        Time.at(self).utc.strftime('%H:%M')
    end
end

開始と終了時間、及び長さの時間表示フォーマットをここで定義しちゃいます。
durationは整数なんでIntegerクラスに変換メソッドを定義しておきます。

で、start_at,end_atは勝手にそのフォーマットで表示され、durationは、duration.time_durationってやれば時:分で表示されるように。

後は、config/application.rb で日本だよってやって、
    config.time_zone = 'Tokyo'
    config.active_record.default_timezone = :local
    config.i18n.default_locale = :ja

config/locales/ja.yml を用意して
ja:
  activerecord:
    models:
      program: 番組
    attributes:
      program:
        title: タイトル
      programs:
        title: タイトル
  title: タイトル
  start_at: 開始時間
  end_at: 終了時間
  duration: 長さ
  channel: チャンネル
  category: カテゴリ
 みたいにしておくとリストヘッダとか諸々日本語になる。
 プライベートアプリなので、直接日本語書いてしまえばいいと思うが、遠回りすることが実験なのでね。

2015/07/11

epgのデータベース登録

シンプルなサーバーアプリが出来たところで番組データを取り込んで、リストアップするところまで。

以前は素のRubyスクリプトでActiveRecordを直接使う流れでしたが、今回はRailsのタスクで用意してみる。

lib/tasks/epg.rakeファイルを作成。以下の様な感じになった。

タスクからタスクを呼ぶのは、executeとinvokeがあるが、invokeは処理中に1度しか呼ばないという仕様がある。チャンネル分回すので、今回はexecuteにした。

タスク引数は、Rake::TaskArgumentsを使うことになるが、executeの場合シンプルに渡すだけらしいので、execute(ch)ってやれば、|task,arg|のargに直接chが入るので、スッキリはする。
ただし、executeからしか呼べなくなるという制約がつく。外から引数付きのタスクとして呼べなくなる。

それから、:updateはただ呼ぶだけのタスクだから :environment は不要だろうと思ったが、親タスクも必要だった。因みに、 :environment はActiveRecordでのレコードクラスインスタンス作成などをやってくれるところらしい。詳しくは知らないっすが・・

後は、んん?というところは無いはず。ごく自然な流れ。


namespace :epg do

  EPG_PATH = Rails.root.join('tmp').join('epg')
  Chs = [27,26,25,24,23,22,21,16]
#  Chs = [27]

  desc "EPG 更新"
  task :update => :environment do
    Chs.each do |ch|
      arg = Rake::TaskArguments.new([:ch],[ch])
      Rake::Task['epg:get'].execute(arg)
      Rake::Task['epg:put'].execute(arg)
#      Rake::Task['epg:get'].execute(ch) ##1 個別に呼ぶのを諦めれば、こっちでもいける.
    end
  end

  desc "EPG 取得"
#  task :get do |t,arg| ##1
  task :get,[:ch] do |t,arg|
    `recfsusb2n -b -i epg #{arg.ch} 10 #{EPG_PATH}/#{arg.ch}.ts`
    `epgdump json #{EPG_PATH}/#{arg.ch}.ts #{EPG_PATH}/#{arg.ch}.json`
  end

  desc "EPG 登録"
  task :put,[:ch] => :environment do |t,arg|
    json = open("#{EPG_PATH}/#{arg.ch}.json").read
    data = ActiveSupport::JSON.decode(json)

    if data.present?
      # Channelレコード 登録
      Channel.create( ch: arg.ch,
                      name: data[0]['name'],
                      code: data[0]['id']) if Channel.where(ch: arg.ch).empty?

      # 過去のプログラムを削除
      Program.destroy_all ["end_at < ?",Time.now]

      # 登録
      data[0]['programs'].each do |prog|
        set_program( prog )
      end
    end
  end

  # レコード 登録
  def set_program( prog )
    if Program.where(event_id: prog['event_id']).empty?

      # category
      category = prog['category']
      if category.present?
        cate = Category.where(name:
                              [category[0]['large']['ja_JP'],' (',category[0]['middle']['ja_JP\
'],') '].join
                              ).first_or_create
      end

      # program
      Program.create( channel_id: Channel.where(code: prog['channel']).first.id,
                      event_id: prog['event_id'],
                      title: prog['title'],
                      detail: prog['detail'],
                      start_at: Time.at(prog['start']/10000),
                      end_at: Time.at(prog['end']/10000),
                      duration: prog['duration'],
                      category_id: cate.try(:id) )
    end
  end
end

2015/07/10

録画システムをrails4で開発

録画ツールや開発環境が整いましたので、いよいよ録画システムのフロントエンドとなる
サーバーアプリケーションをRails4を使って構築していきます。

mysql

用途から言ってsqlliteで十分だと思うけれど、データベースは使い慣れているmysqlにする。

$ sudo apt-get install mysql-server
$ sudo apt-get install libmysqlclient-dev ← gem mysqlで使うから

後これくらいはやっておこう。
$ mysql_secure_installation

ユーザ作成
mysqlにrecmanユーザを作成して、recmanデータベースのアクセス権を与える。

$ mysql -uroot -p
> create user 'recman'@'localhost' identified by 'pw';
> grant all privileges on recman.* to 'recman'@'localhost';


railsアプリ(recman)の作成
$ rails new recman -d mysql

gitリポジトリ
$ cd recman
$ git init
$ git add .
$ git commit -m "the first recman"

gem
recman/conf/database.ymlのproductionセクションが以下のようになっていたので
production:
  <<: br="" default="">  database: recman_production
  username: recman
  password: <%= ENV['RECMAN_DATABASE_PASSWORD'] %>
 ↑直接パスワードに書き換える。環境変数で管理するほどじゃないし。
その他データベース名とかは自分のすきに書き換える。

Gemfileに追記
gem 'therubyracer'
gem 'mysql'
gem 'unicorn' ←productionをunicornで駆動するから


今回デプロイしないで直接駆動の予定なので、capistranoは使わないな。

$ bundle install

これでrailsアプリの母体が出来ました。

model作成

Channelモデル
$ rails g model channel code:integer name:string

Categoryモデル
$ rails g model category name:string

Programモデル
$ rails g scaffold program \
    channel_id:integer \
    title:string \
    detail:text \
    start_time:datetime \
    end_time:datetime \
    duration:integer \
    category_id:integer \
    event_id:integer

 scaffoldで作ると、VMC全部作ってくれます。楽ちん。
event_idはどう使うかイメージできていないけど、放送終了まではユニークなコードらしいので録画スケジュールとかで使えるかと。

後で、ChannelとProgramのchannel_idが関係付けられる感じ。Categoryも同じ。


 index作成
モデル作成時に出来たmigrationファイルに書き加えてもいいけど、区別した方が自分は好きなので。

$ rails g migration AddIndexToPrograms

db/migration/[TIME]_add_index_to_programs.rbにadd_indexを書いていく
 def change
    add_index :programs, :channel_id
    add_index :programs, :title
    add_index :programs, :category_id
    add_index :programs, :start_at
    add_index :programs, :end_at
    add_index :programs, :event_id
    add_index :programs, [:start_at,:end_at]
    add_index :programs, [:start_at,:duration]
    add_index :programs, [:category_id,:start_at]
 end
な感じ? ありそうなクエリをひと通りインデクス作っておこう。マスターレコードのChannelやCategoryも一応ね。

データベース作成
$ bundle exec rake db:create
$ bundle exec rake db:migrate


viewを確認(データ入ってないけど)
$ rails s -b 0.0.0.0

ブラウザで http://shuttle:3000/programs へ

Listing Programs

Channel_id Title Detail Start_at End_at Duration Category_id

New Program
とりあえず出ましたね。

おお。パスをわざと間違えてみたら、RoutingErrorページが格好いいじゃないですか。
多分これDevelopmentで動かしている時に出るんでしょうね。Rails3はこんなじゃなかったなぁ。
ちょっと感動。

データベースの中身が入ってこないとつまらないんで、次はそっちから片付けます。

2015/07/08

rvm + rails4 でサーバーアプリ開発環境

いつもの流れではあるので特出すべきことはないですが・・・
Shuttleで録画システム再開発ということで、前回の反省点を踏まえながら進めていく。

まずは、rvmとrails4.2 railsは仕事では3までしか使ってないんで4は自分的に初!

rvm

$ sudo apt-get intall curl

-- ここからは全部自分環境なので sudo じゃないよ --

$ curl -sSL https://rvm.io/mpapis.asc | gpg --import -
$ curl -sSL https://get.rvm.io | bash -s stable
$ source /home/shuttle/.rvm/scripts/rvm ← shuttleというユーザ名でやってます
$ rvm list known ←選択可能なrubyバージョンを確認
$ rvm install ruby-2.2.1 --default

これが長いんだ・・・暫し待つ。
この時点で gemとgem基本パッケージが入っている

rails4.2

$ gem i rails --no-ri --no-doc

これも長いよねぇ・・・


アプリ作成

$ rails new recman ←という名のアプリ作成
$ cd recman
$ bundle install
$ rails s

とりあえずWebrickでHTTPサーバー起動するかな?

あぁ、そうだった。developmentでWebrick起動するためには、'therubyracer'が必要でした。
Gemfileに、gem 'therubyracer' を追記。

もう一度
$ bundle install
$ rails s

別のPCから、http:://shuttle:3000/ へアクセス。およ?アクセス出来ない・・・?

最初は、ufw とか考えたけれど、いやいやEnforceしてないし・・・結局調べたら
何と rack version 1.6から仕様が変わったらしく、デフォルトが http://localhost:3000 となるらしい。
要するに自分自身以外からのアクセスが出来ないと。

これだと困るので

$ rails s -b 0.0.0.0

ってやると、どこからでもアクセスできるようになる。むぅ、ちょっとめんどくさいな。
rails4.2環境は初めてなので、こんなところでも引っかかってしまう。

2015/07/07

epgdump と ffmpeg + x264 + aac 再び

2年前にやったことだけれど Shuttle でも同じように使いたいので、
まずは標準的なセットアップをしておく。

epgdump

系統が幾つかあるようなのだが、json使いたいんで、こっちを使ってみる
$ git clone https://github.com/Piro77/epgdump.git

cmakeいるらしい
$ sudo apt-get install cmake

ビルド
$ cd epgdump
$ cmake .
$ make

xml,json,csv共にやってみた。良好である。

ffmpeg

こっちは後で手を加えていきたいところだけど、今は標準的なビルドをしておく

参考。英文だけど、情報に無駄がない
https://trac.ffmpeg.org/wiki/CompilationGuide/Ubuntu

取得
$ git clone https://github.com/FFmpeg/FFmpeg.git

必要最低限のライブラリインストール
$ sudo apt-get install yasm libx264-dev libfaac-dev
yasm入れておかないとcpuエンコ速くなりません。

コンフィグ実行
./configure \
  --enable-gpl \
  --enable-libfaac \
  --enable-libx264 \
  --enable-nonfree

ビルド & インストール
$ make
$ sudo make install
/usr/local/binにffmpeg,ffprobe,ffserverが。その他/usr/local/に開発用ファイルが入る。

テスト
$ recfsusb2n -b -i hd 21 30 test.ts
$ ffmpeg -y -i test.ts -c:v libx264 -c:a libfaac -preset superfast -f mp4 -threads 0 test.mp4

はい。滞りなく。

qsv対応?
nvencのIntelCPU版みたいなやつの組み込みで高速化とかもいずれ。


さあ、ここから新しい録画システム作りの始まりです。

recfsusb2n --http と リモートVLC再生

$ recfsusb2n -b -H 8888
でデーモン起動して、愛機iMacのVLCから’http://shuttle:8888/27'とかで再生されるはず。
と思ったけど若干手を加える必要があった。

VLCでアクセスした時に、recfsusb2n内の'gethostbyaddr'関数でコケてしまい
デーモンが終了してしまう。

fsusb2n.cpp内の問題箇所をざっくりコメントアウト
#if 0
                        peer_host = gethostbyaddr((char *)&peer_sin.sin_addr.s_addr, sizeof(peer_saain.sin_addr), AF_INET);
                        if ( peer_host == NULL ){
fprintf(stderr, "gethostbyname failed\n");
                                exit(1);
                        }

                        fprintf(stderr,"connect from: %s [%s] port %d\n", peer_host->h_name, inet_ntoa(peer_sin.sin_addr), ntohs(peer_sin.sin_port));
#endif
どこからアクセスされたかを表示したいだけの箇所らしいので、特に不要。

この修正を加えたrecfsusb2nを再度ビルドして、デーモン起動して、他のPCからのVLCアクセスで無事再生されました。

よかった〜

VLCのネットワークストリーム再生で、http:://shuttle:8888/24とかで綺麗に再生される。
iPadのVLCやVLC for Android、VLC for Fireでも綺麗に再生された。
それ以外の亜流品はダメだった。