[Python]Apache + Gunicorn + hug で簡単APIサーバ

先日、Apacheとmod_wsgiPythonを動かし、APIサーバを作成してみました。

kittagon.hateblo.jp

しかし、hugに備わっているBasic認証を追加することは出来ませんでした。

その後Gunicornを試してみたら、驚くほど簡単に実現できてしまいました。。

最初からこっちにすればよかった。。

前提

Pythonの環境とアプリケーションは前回のものを使います。

import sys,json,os
import hug
import util
from dotenv import load_dotenv

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)

HUG_USER_NAME = os.environ.get("HUG_USER_NAME")
HUG_PASSWORD = os.environ.get("HUG_PASSWORD")

authentication = hug.authentication.basic(hug.authentication.verify(HUG_USER_NAME, HUG_PASSWORD))

@hug.get("/hoge",,requires=authentication)
def hoge(text):
    hoge_list = util.getHogeList(text)
    return json.dumps({"hoge":hoge_list})

前回記述したsys.path.append('/var/www/app')はなくても今回は正常に動作しました。

また、動作させるアプリケーションは、gunicornを起動する際にパラメータとして渡すので、application = __hug_wsgi__も不要です。

Gunicornのインストール

pipでインストールします。

$ pip3.6 install gunicorn

これで完了です。

$ cd /var/www/app/
# gunicornの起動
$ gunicorn app:__hug_wsgi__
[2017-09-12 22:54:04 +0000] [3994] [INFO] Starting gunicorn 19.7.1
[2017-09-12 22:54:04 +0000] [3994] [INFO] Listening at: http://127.0.0.1:8000 (3994)
[2017-09-12 22:54:04 +0000] [3994] [INFO] Using worker: sync
[2017-09-12 22:54:04 +0000] [3997] [INFO] Booting worker with pid: 3997

アプリがポート8000で起動します。

$ curl http://localhost:8000/hoge?text=hoge

結果が帰ってくればOKです。(Basic認証をかけているので認証エラーとなりますが。)

Apacheの設定

次にApacheの設定をします。

/etc/httpd/confd/httpd.confに以下の1行を記述し、リクエストをすべてgunicornに流すよう設定します。

ProxyPass / http://localhost:8000/

gunicornを起動した状態で、ブラウザからhttp://192.168.33.10/hoge?text=hogeにアクセスして認証画面がでて認証できればOK!なのですが、なにやらまた、「Internal Server Error」が。。。

前回とまた同じです。

SELinuxの無効化

Apacheのエラーログを見ると、

[Tue Sep 12 12:03:36.233033 2017] [proxy:error] [pid 5746] (13)Permission denied: AH00957: HTTP: attempt to connect to 127.0.0.1:8000 (localhost) failed
[Tue Sep 12 12:03:36.233187 2017] [proxy:error] [pid 5746] AH00959: ap_proxy_connect_backend disabling worker for (localhost) for 60s
[Tue Sep 12 12:03:36.233202 2017] [proxy_http:error] [pid 5746] [client 192.168.33.1:62957] AH01114: HTTP: failed to make connection to backend: localhost

proxyがうまくいっていないみたいです。

エラーログを調べてみると色々情報がありました。

SELinuxが原因で、どうやらよくあることらしいです。

Linux:CentOS6でmod_proxyが動かない

こちらを参考にしました。

/var/log/audit/audit.logを見てみると、たしかに以下のエラーが。

type=AVC msg=audit(1505218092.201:865): avc:  denied  { name_connect } for  pid=5745 comm="httpd" dest=8000 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:soundd_port_t:s0 tclass=tcp_socket

/etc/sysconfig/selinuxを修正して、SELINUX=disabledとします。

OSを再起動して、再度gunicornを立ち上げると、無事動作しました。

Basic認証も問題なく、きちんと認証できます。

gunicornの自動起動設定

OSを立ち上げたときにApache自動起動するよう設定しましたが、gunicornも自動起動するように設定しないとアプリをWebサーバで動作させられません。

SYSTEMCTLでDJANGOを自動起動する

ここに記載されているとおりに実行すればOKです。

$ cd /etc/systemd/system/
$ vim app.service

今回gunicornのパスは/bin/gunicornで、アプリケーションのディレクトリは/var/www/appなのでapp.serviceの中身は以下のようになりました。

[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=apache
Group=apache
WorkingDirectory=/var/www/app
ExecStart=/bin/gunicorn schedule:__hug_wsgi__

[Install]
WantedBy=multi-user.target

自動起動するように設定します。

$ systemctl enable app
$ systemctl start app

OSを再起動し、ブラウザからhttp://192.168.33.10/hoge?text=hogeにアクセスして結果が出ればOKです。

まとめ

前回mod_wsgiでアプリケーションを動作させたよりももっと簡単に実現できました。

また、前回躓いたBasic認証も正常に動作させることが出来ました。

今回はgunicornを運用していこうと思います。

参考ページ

*比較 Apache+gunicornのベンチマークを取った方がいました。

さくら VPS 1G の CentOS で Apache + gunicorn のベンチマークをとってみた

[Python]CnetOS7 + Apache + mod_wsgi + hug で簡単APIサーバ作成

簡単なAPIサーバを作りたいなーと思い、やってみました。

以前はPHPを少ししか書けなかったけど、もう少し色々な言語を使いたいと思いPythonで実装してみることにしました。

色々と調べてみるとPythonにもフレームワークは色々とあるようで、DjangoとかFlaskとかありましたが、簡単に実装できそうなhugを使うことにしました。

PHPだとWebサーバ上で動かすのは簡単だったのですが、Pythonだと割と大変だったので備忘録として残しておきます。

wsgiサーバはpythonだとgunicornとかuwsgiとかがポピュラーみたいでしたが、何やら設定が難しそうな印象を受けたので、なんとなく簡単そうなmod_wsgiを使うことにしました。

最終的にやりたかったBASIC認証までを追加することは出来ませんでしたが。。。

知っている方がいたら教えてほしいです。。

※20170914 追記 gunicorn使った方が簡単でした。

kittagon.hateblo.jp

Vagrantを使ってCentOSVMを作成

Vagrantfileはこんな感じです。

Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"
  config.vm.network "private_network", ip: "192.168.33.10"
"192.168.0.100" , :bridge => "en0: Wi-Fi (AirPort)"
  config.vm.synced_folder "./", "/vagrant", type: "virtualbox", :owner => "vagrant", :groupe => "vagrant", :mount_options => ['dmode=755', 'fmode=655']
  config.vm.provision "shell", run: "always", inline: "systemctl restart network.service"
end

Vagrant+Laravelでパーミッションで詰まった」とか、「vagrant + centos7 でprivate_networkで設定したIPに接続ができない」とかで躓いた経緯があったから、

config.vm.synced_folder "./", "/vagrant", type: "virtualbox", :owner => "vagrant", :groupe => "vagrant", :mount_options => ['dmode=755', 'fmode=655']
config.vm.provision "shell", run: "always", inline: "systemctl restart network.service"

とかを記載しています。

Python3系とApacheのインストール

hugはPython3系でしか動かないので、まずはPython3系をインストールします。

Apacheyumでインストールすればいいのですが、Pythonに関してはソースからコンパイルしたりyumで入れたりとか色々やり方はあったのですが、最終的に「Python 3 を CentOS 7 に yum でインストールする手順」のやり方を参考にしたら上手くいきました。

[Python] mod_wsgiを使ってPython3.6をApacheで動かす(CentOS6系)

このあたりを参考にソースからコンパイルする方法も試してみましたが、何故かpipでmod_wsgiをインストールするところでつまずいてしまいました。Cent0S6系と7系の違い?

とりあえず下記コマンドでインストールします。

# 色々インストール
$ yum groupinstall -y "Development tools"
$ yum install -y zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel
# Apacheのインストール
$ yum install -y httpd httpd-devel
# OS起動時にApacheの起動を有効化
$ systemctl enable httpd.service
# Apacheの起動
$ systemctl start httpd.service
# Pyton3.6.xのインストール
$ yum install -y https://centos7.iuscommunity.org/ius-release.rpm
$ yum install -y python36u python36u-libs python36u-devel python36u-pip

$ python3.6 -V
Python 3.6.2

無事Python3.6とApacheがインストール出来ました。

mod_wsgiのインストール

こちらもソースからインストールしたりpip経由でのインストールがあるみたいですが、あえてソースからインストールするメリットも分からなかったので、pip経由でインストールすることにしました。

先程の[Python] mod_wsgiを使ってPython3.6をApacheで動かす(CentOS6系)の後半辺りを参考にしています。

$ pip3.6 install mod_wsgi

$ python3.6 -c "import sys; print(sys.path)"
['', '/usr/lib64/python36.zip', '/usr/lib64/python3.6', '/usr/lib64/python3.6/lib-dynload', '/usr/lib64/python3.6/site-packages', '/usr/lib/python3.6/site-packages']

インストールが完了し、パケージのインストール先が/usr/lib64/python3.6/site-packagesだと分かります。

$ cd /usr/lib64/python3.6/site-packages
$ ll
total 8
drwxr-xr-x. 6 root root 4096 Sep  5 13:19 lxml
drwxr-xr-x. 2 root root  131 Sep  5 13:19 lxml-3.8.0.dist-info
drwxr-xr-x. 6 root root   84 Sep  5 09:15 mod_wsgi
drwxr-xr-x. 2 root root  141 Sep  5 09:15 mod_wsgi-4.5.18-py3.6.egg-info
drwxr-xr-x. 2 root root    6 Jul 19 04:01 __pycache__
-rw-r--r--. 1 root root  119 Jul  8 03:33 README.txt

$ cd mod_wsgi/server
$ ll
total 1048
-rw-r--r--. 1 root root   1558 Sep  5 09:15 apxs_config.py
-rw-r--r--. 1 root root   3563 Mar 31  2016 environ.py
-rw-r--r--. 1 root root 128159 Aug 29 09:28 __init__.py
drwxr-xr-x. 4 root root     60 Sep  5 09:15 management
-rwxr-xr-x. 1 root root 932872 Sep  5 09:15 mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so
drwxr-xr-x. 2 root root    101 Sep  5 09:15 __pycache__

ここにある、mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.soを使います。

パスは/usr/lib64/python3.6/site-packages/mod_wsgi/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.soserverです。

Apacheの設定

これで必要なものはインストール出来たので、Apacheの設定をしていきます。

/etc/httpd/conf.d/配下にwsgi.conf設定ファイルを配置し、wsgiの設定を記述します。

LoadModule wsgi_module /usr/lib64/python3.6/site-packages/mod_wsgi/server/mod_wsgi-py36.cpython-36m-x86_64-linux-gnu.so

WSGIDaemonProcess myapp user=apache group=apache
WSGIProcessGroup myapp
WSGISocketPrefix /var/run/wsgi
WSGIScriptAlias / /var/www/app/app.py

<Directory /var/www/app/>

  Options ExecCGI MultiViews Indexes
  MultiViewsMatch Handlers

  AddHandler wsgi-script .py
  AddHandler wsgi-script .wsgi

  DirectoryIndex index.html index.py app.py

  Order allow,deny
  Allow from all

</Directory>

これを記述したらApacheを再起動しておきます。

$ apachectl restart

これで、ルートディレクト/を開いたときに、/var/www/app/app.pyを動作させるという設定になりました。

hugでapp.pyの作成

とりあえずこんな感じで書きました。

import sys,json,os
import hug
import util

@hug.get("/hoge")
def hoge(text):
    hoge_list = util.getHogeList(text)
    return json.dumps({"hoge":hoge_list})

今回はGETで受け取ったパラメータを他の関数に渡して処理してもらい、その結果を返すAPIを想定しているので、この様な書き方にしています。

実際に処理する関数はutil.pyに記載しています。

以下コマンドで、開発用サーバを起動します。

$ hug -f app.py

/#######################################################################\
          `.----``..-------..``.----.
         :/:::::--:---------:--::::://.
        .+::::----##/-/oo+:-##----:::://
        `//::-------/oosoo-------::://.       ##    ##  ##    ##    #####
          .-:------./++o/o-.------::-`   ```  ##    ##  ##    ##  ##
             `----.-./+o+:..----.     `.:///. ########  ##    ## ##
   ```        `----.-::::::------  `.-:::://. ##    ##  ##    ## ##   ####
  ://::--.``` -:``...-----...` `:--::::::-.`  ##    ##  ##   ##   ##    ##
  :/:::::::::-:-     `````      .:::::-.`     ##    ##    ####     ######
   ``.--:::::::.                .:::.`
         ``..::.                .::         EMBRACE THE APIs OF THE FUTURE
             ::-                .:-
             -::`               ::-                   VERSION 2.3.1
             `::-              -::`
              -::-`           -::-
\########################################################################/

 Copyright (C) 2016 Timothy Edmund Crosley
 Under the MIT License


Serving on port 8000...

開発用のサーバがポート8000番で立ち上がるので、 $ curl http://localhost:8000/hoge?text=hoge で結果が帰ってくればOKです。 また、http://192.168.33.10:8000/hoge?text=hogeにアクセスでも見れます。

開発用サーバではなく、mod_wsgi経由でApacheでも動作しているか確認したいので、mod_wsgiでも動くようにします。 mod_wsgiで動作させるには、mod_wsgiに対応したapplicationを作る必要があります。

mod_wsgiでhugを使い、APIを作るPython hug with apache mod_wsgiを参照。

app.pyの最下部に以下の一文を追記します。

application = __hug_wsgi__

ブラウザで、http://192.168.33.10/hoge?text=hogeにアクセスして同じ結果が帰ってくればOK。

・・・なのですが、何やら「Internal Server Error」のエラーが。

Apacheのエラーログを見てみると、

[Sat Sep 09 13:28:00.511142 2017] [wsgi:error] [pid 7361] [remote 192.168.33.1:57939] mod_wsgi (pid=7361): Target WSGI script '/var/www/app/app.py' cannot be loaded as Python module.
[Sat Sep 09 13:28:00.511233 2017] [wsgi:error] [pid 7361] [remote 192.168.33.1:57939] mod_wsgi (pid=7361): Exception occurred processing WSGI script '/var/www/app/app.py'.
[Sat Sep 09 13:28:00.511368 2017] [wsgi:error] [pid 7361] [remote 192.168.33.1:57939] Traceback (most recent call last):
[Sat Sep 09 13:28:00.511434 2017] [wsgi:error] [pid 7361] [remote 192.168.33.1:57939]   File "/var/www/app/app.py", line 4, in <module>
[Sat Sep 09 13:28:00.511441 2017] [wsgi:error] [pid 7361] [remote 192.168.33.1:57939]     import util
[Sat Sep 09 13:28:00.511469 2017] [wsgi:error] [pid 7361] [remote 192.168.33.1:57939] ModuleNotFoundError: No module named 'util'

なにやらutil.pyが読み込めていない模様。

Pythonで「ImportError: No module named …」が出た時の3つの対処法を参考に、sys.path.append('/path/to/dir')app.pyに追記すると直りました。

mod_wsgiでは若干挙動が違う?

Basic認証を追加

The guiding thought behind the architectureを参照すると、getの引数にhug.authentication.verify("userid","password")を与えるとBasic認証が出来るようになるみたいです。

そのままユーザーID、パスワードを記述するのも良くないので、.envファイルを作成してそこに環境変数を書くとします。

【GitHub】に載せたくない環境変数の書き方 Python こちらにdotenvの使い方が載っています。

$ pip3.6 install python-dotenv

.envの中身は↓にしました。

HUG_USER_NAME=uname
HUG_PASSWORD=pass

この中の環境変数は、以下のように取り出します。

import os
from os.path import join, dirname
from dotenv import load_dotenv

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)

HUG_USER_NAME = os.environ.get("HUG_USER_NAME")
HUG_PASSWORD = os.environ.get("HUG_PASSWORD")

これらの変数を用いて以下のようにBasic認証の設定を記述します。

import sys,json,os
sys.path.append('/var/www/app')
import hug
import util
from dotenv import load_dotenv

dotenv_path = join(dirname(__file__), '.env')
load_dotenv(dotenv_path)

HUG_USER_NAME = os.environ.get("HUG_USER_NAME")
HUG_PASSWORD = os.environ.get("HUG_PASSWORD")

authentication = hug.authentication.basic(hug.authentication.verify(HUG_USER_NAME, HUG_PASSWORD))

@hug.get("/hoge",,requires=authentication)
def hoge(text):
    hoge_list = util.getHogeList(text)
    return json.dumps({"hoge":hoge_list})
    
application = __hug_wsgi__

開発用サーバを立ち上げて、ブラウザからhttp://192.168.33.10:8000/hoge?text=hogeに認証画面が出て認証できればOKです。

Apacheからの接続はどうかというと、http://192.168.33.10/hoge?text=hogeに接続すると、無事認証画面が出ました!!!

が、、IDとパスワードを入れても認証出来ません。。。。

いくら調べても原因が分からない。。なんでなんだ~。。。。

今回はここで断念しました。。。

まとめ

mod_wsgiとhugを使って、簡単なAPIサーバを立ち上げることが出来ました。

ただ、最終的にやりたかったBasic認証はなぜかmod_wsgiだと正常に動作しませんでした。

hugに組み込まれている開発用のサーバとmod_wsgiとでは若干挙動が違うようです。

解決策を知っている方が居ましたら教えて下さい。

参考ページ

銭湯で痴漢された話

僕は昔からホモにモテる。何故かわからないけど。 昔は運動もしていたし、体を使うバイトもしていたので割りと筋肉質でムチムチした体をしていたからかも。

blog.livedoor.jp

ちょっと前こんな記事を見て、昔銭湯で痴漢されそうになったことを思い出しました。 しかもスーパー銭湯の系列店も同じだし。。。。

犯行現場はやはり泡風呂

先の記事でも泡風呂について言及されてましたが、自分が被害にあったのも泡風呂でした。

友人三人でスーパー銭湯に行き、泡風呂に↓のように座っていました。

┌────────────────┐
│                │
│                │
│  他 友 友 自  他    │
└────────────────┘

友人三人で並んでお湯に浸かり、自分が端でした。

ゆっくりと他愛もない会話をしながらお湯に浸かっていると、前に伸ばしている自分の膝周りちょいちょい触れるものが。。。

銭湯でたまに体が当たってしまうのはよくあることなので、その時点では何も思わず、「なんか当たんだけど〜、狭いわ〜」と友達にもぼやきながら、足を動かして跳ね除け?たりしていました。

しかし、跳ね除けても跳ね除けても何かが足に触れてきます。自分と隣の人との間には十分なスペースがあり、普通にじっとしていれば体が触れ合うような距離ではなかったのです。

しかも、触れてくる場所が少しずつ上に来て、太もも周りを触られている感触が。

気付いても何も出来ない

「隣のおっさんがこっちに手伸ばして触ろうとしてきてね?!」

そう気づいてしまったときに僕は恐怖に陥りました。

隣のおっさんは普通の何食わぬ顔で前を向きお湯に浸かっているように見えるけど、お湯の中をちらっと見ると不自然な体勢になりながらも手をこちらに伸ばしてきている!!!

恐怖を感じながらも抵抗する?ことも出来ないので、隣の友人に「サウナ行こうぜ!」と言って逃げました。

その後友人に恐怖を語ったものの、笑いものにされたのは言うまでもありません。。。

まとめ

銭湯コワイ ホモコワイ いや、痴漢コワイ

電車で痴漢されて助けを呼べない女性の気持ちが分かったなぁ〜。。

Nexus5XにAndroidOを手動インストールした手順

先日GoogleがAndroidOの開発者プレビューを公開しました。

会社での検証が必要だったので、手動インストールをしてみました。

そもそも普段からiPhoneユーザーだしAndroidを使ったことも無いので、とにかく調べながらの実践だったので、最終的に何が正解だったのかわかりませんでした。

しかし、どうにかアップデートできたので備忘録として記しておきます。

端末

  • Nexus5X(Android7.1.2)
  • MacBookAir 13-inch, Early 2015(10.11.6 OSX El Capitan)

AndroidStudio AndroidSDKのインストール

何やらAndroidを手動でインストールするためには、AndroidSDKが必要で、adb?とか言うコマンドで実施するとのこと。 AndroidStudioは必要ではないっぽいけど、後々アプリ開発もするかもしれなかったのでAndroidStudio経由でAndroidSDKをDLしました。

HomebrewにてAndroidStudioをインストール

$ brew cask install android-studio

インストールされたAndroidStudioを初回起動すると、AndroidSDKをインストールするかを聞かれるので、適当にダウンロードします。 (この辺りはどうやったか詳細を忘れました。)

するとSDKがDLされます。多分デフォルトだと大体同じかと思われます。 (自分の環境だと```$HOME/Library/Android/sdk/)にインストールされました。

このsdkフォルダ以下のplatform-toolsへのパスを通します。

$ echo "export PATH=$PATH:$HOME/Library/Android/sdk/platform-tools" >> ~/.bash_profile
$ source ~/.bash_profile

これでadbなどのコマンドが使えるようになります。

Nexus5Xを開発者モードにしUSBデバッグをオンにする

以下を参考に実施。

Android端末を「デバッグモード」(USBデバッグ)に設定する

システムイメージのダウンロード

以下より、端末に合ったファイルをダウンロードします。

Android O のダウンロード | Android Developers

Nexus5Xの場合はbullhead-opp1.170223.012-factory-338a95b1.zipというファイルで1.5Gバイトほどのサイズでした。

リカバリーモードを起動

ここからは以下を参考に実施。

Nexus端末を手動でAndroid5.0にアップデート | うめの記録帳

端末を「adb sideload」を使って手動で即座にアップデートする方法

NEXUSデバイスをSDK、adb sideloadを利用して最新ビルドのAndroidOSに手動で強制的にアップデートする方法

Nexus5Xの電源を切り、ボリュームダウンボタンを押し続けながら電源ボタン長押しで起動すると、以下のようにファストブートモードの画面になります。

f:id:ti_taka:20170417232622j:plain

ボリュームボタンのアップとダウンで選択が出来るので、リカバリーモードに合わせ、電源ボタンで選択します。すると以下のような画面になります。

f:id:ti_taka:20170417232648j:plain

電源ボタンを押しながらボリュームアップボタンを押してリカバリーメニューを表示します。

f:id:ti_taka:20170417232712j:plain

ボリュームボタン上下で、Apply update from ADBを選択すると、以下のような画面になります。

f:id:ti_taka:20170417232736j:plain

ここからがPC側の作業で、ターミナルを起動し、先程ダウンロードしてきたシステムファイルbullhead-opp1.170223.012-factory-338a95b1.zipがあるディレクトリに移動します。

ここで、以下コマンドを実行します。

$ adb sideload bullhead-opp1.170223.012-factory-338a95b1.zip

これでアップデート出来るはずだと思っていたが、以下のようなメッセージが出て終わってしまった。

loading: 'bullhead-opp1.170223.012-factory-338a95b1.zip'...
connecting...
Total xfer: 0.00x

Nexus5X側では以下のようなエラーメッセージが。。。

f:id:ti_taka:20170417232806j:plain

次に、以下のページを参考に、別のやり方を試してみた。

Android 6.0 Marshmallowを手動アップデート/インストールする方法

何やら、oem unlockというのが必要らしい。 以下を参考に実施。 Nexus5X/Nexus6Pのブートローダーアンロック方法・手順。

Nexus5Xの設定→開発者向けオプション→OEMロック解除を有効にする

再度Nexus5Xでファストブートモードを起動し、PCに接続します。 ターミナルより、以下コマンドを実行します。

$ fastboot oem unlock

すると以下のようにロックを解除しても良いか聞かれるので、ボリュームボタンでYesを選択し、電源ボタンで決定します。

f:id:ti_taka:20170417232907j:plain

その後しばらくすると再起動し、端末が初期化されます。

f:id:ti_taka:20170417233021j:plain

再度ファストブートモードを起動すると、下部のDEVICE STATEがunlockedになりました。

f:id:ti_taka:20170417233316j:plain

PC側で、bullhead-opp1.170223.012-factory-338a95b1.zipを解凍して出来たディレクトリの中に移動し、中にある./flash-all.shを実行する。

$ cd bullhead-opp1.170223.012
$ ./flash-all.sh

Nexus5X側では以下のようにdownloadingという文字が表示され、正常に進行していることが分かります。

f:id:ti_taka:20170417233550j:plain

これにて無事アップデートが完了しました。

参考

想像力がないことについて

anond.hatelabo.jp

だいぶ前の記事だがこれを読みました。あまりに自分と同じだったので「オレのことか?」と思いびっくりしました。 ただ自分は、小さい頃特にゲームばかりをやっていたわけでもなく、そこまで多い方とまでは言えないがそれなりに読書は好きだったので本も読んでいました。 なので、ゲームをやりすぎたことが想像力がない原因と結論付けることに違和感を感じました。 そこで、今回は想像力がないことの原因について考えてみました。

想像力がないことは悪いことだけではないのでは?

まず前提として、自分は想像力がないという自覚があるが、それは純粋に想像することができないという意味ではなく、想像する力を常日頃から働かせる力がない、つまり想像する習慣がないという状態だという認識です。 何が言いたいかというと、自分の場合は想像しようと思えばできると思っている、ということ。(それが本当にそうかは別として)

では、なぜできないか?

それは結局そういう習慣がないから、ということに尽きるが、習慣がないのはないなりの理由があると考えます。 理由としては、想像する必要がないような何かがあったから想像しなかった、ということが考えられます。 想像力がないということは、捉え方を変えれば既知の情報を用いて効率的に大枠を捉えているとも言えます。 想像力がないのは、ゼロから情景を構築しないで済むように、既知の景色で補完しているということなのではないでしょうか。

でもそれはつまり先入観をもって物事を捉えているだけにすぎない

既知の情報を元に目の前の事象に触れている。つまり、想像力がない=先入観、バイアスを持って物事を捉えること 複眼的思考法では、冒頭部分でこのバイアスを持って物事を捉えることの意味と、危険性について論じています。

先入観をもって物事を捉えるのは効率的で、素早い判断をするには役に立つ。 この説明には納得がいきます。 自分も想像力はないが、本を読むのは早い方でした。

しかし、やはりこれだと細い部分は抜け落ちてしまう。

入社してすぐのときに、かんたんな開発の案件に参画しました。 要件が定まらない中でも大枠を捉えてプロトタイプをどんどん作って開発を進めたことで、比較的スムーズに開発をすすめることができました。 しかし最終局面になったときに、使用するライブラリのクリティカルな仕様を読み落としていることに気づき、大変な目にあいました。 (幸い開発量としては大きい案件ではなかったので休日出勤をすることで解決できたが。。) 多分これも、自分の上記のような性質によるものだと思います。 一部の情報と自分の先入観から大枠を構築するので、確かに行動のスピードは早くはなるが、細かい部分を見落としがちになってしまう、ということなのかもしれません。

まとめ。

  • 想像力がないことは、先入観を持って物事を見ていることと一緒

  • 先入観を持つことで素早い判断ができるようになるが、それでは本質を見落としかねない

  • 自分が得た情報をしっかり検証し、そこから考えを構築することが大切

結局ぼんやりと考えていることを書いただけになってしまったので、次はこうした症状を持っている場合にどうしたら治すことができるのか、といった方法論に落とし込みたいと思います。

Nginxとffmpegで映像プレイヤーの検証環境を構築(RTMP、HLS)

背景

FlowPlayerを仕事で使うことになり、デザインの変更やFlash Failbackの検証などを行うための軽量な環境が欲しかった。

やりたいこと

  • 映像ソースを送る側と、それを配信するサーバーをローカル環境上に立ち上げたい(LIVE配信で!!)
  • 同一LANの他の端末から接続できるようにしたい
  • RTMPとHLSどちらも検証したい
  • コマンドひとつで環境を立ち上げたい!

構成

そこで以下のような構成を作ることにしました。

構成図

コンテナ1の作成(Web、配信サーバ)

要件

  • Webサーバの機能を持つこと
  • RTMPサーバの機能を持つこと
  • HLSのセグメント化もできること

設定ファイル

nginxのRTMPモジュールを使えば、nginxだけで上記の機能のすべてを賄えるとのこと。

インストール方法はこちらを参照。

これをベースにDockerfileを作成します。

FROM centos:latest
RUN yum update -y && \
    yum -y install gcc pcre-devel openssl openssl-devel wget unzip tar git make && \
    useradd --shell /sbin/nologin nginx && \
    wget http://nginx.org/download/nginx-1.10.2.tar.gz && \
    tar -zxvf nginx-1.10.2.tar.gz && \
    wget https://github.com/arut/nginx-rtmp-module/archive/master.zip && \
    unzip master && \
    cd nginx-1.10.2 && \
    ./configure \
      --user=www-data \
      --group=www-data \
      --with-http_ssl_module \
      --with-http_realip_module \
      --add-module=../nginx-rtmp-module-master && \
    make && \
    make install
COPY ./nginx.conf /usr/local/nginx/conf/nginx.conf
WORKDIR /usr/local/nginx/html
EXPOSE 80 1935
ENTRYPOINT ["/usr/local/nginx/sbin/nginx", "-g", "daemon off;", "-c", "/usr/local/nginx/conf/nginx.conf"]

RTMPサーバの設定については、以下の記述をnginx.confに追記すればOKのようです。 参考:nginx-rtmp-module/wiki

rtmp {
    server {
        listen 1935;
        application live {
            live on;
            wait_video on;
            exec ffmpeg -i rtmp://localhost/live/$name
              -c:a libfdk_aac -b:a 32k  -c:v libx264 -b:v 128K -f flv rtmp://localhost/hls/$name_low
              -c:a libfdk_aac -b:a 64k  -c:v libx264 -b:v 256k -f flv rtmp://localhost/hls/$name_mid
              -c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 512K -f flv rtmp://localhost/hls/$name_hi;
        }
        application hls {
            live on;

            hls on;
            hls_path /usr/local/nginx/html/hls;
            hls_nested on;
            hls_fragment 5s;

            hls_variant _low BANDWIDTH=160000;
            hls_variant _mid BANDWIDTH=320000;
            hls_variant _hi  BANDWIDTH=640000;
        }
    }
}

まずrtmp://{IPアドレス}/live/{ストリーム名}にストリームを送ると、 それを受け取ったあと、

exec ffmpeg -i rtmp://localhost/live/$name
  -c:a libfdk_aac -b:a 32k  -c:v libx264 -b:v 128K -f flv rtmp://localhost/hls/$name_low
  -c:a libfdk_aac -b:a 64k  -c:v libx264 -b:v 256k -f flv rtmp://localhost/hls/$name_mid
  -c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 512K -f flv rtmp://localhost/hls/$name_hi;

上記が実行されrtmp://localhost/hls/$name_lowrtmp://localhost/hls/$name_midrtmp://localhost/hls/$name_hiの3ストリームに更に流されるという仕組みのようです。 そして、下記の部分でHLSについての設定が記述してあるので、ここでセグメント化がされるという流れみたいです。

application hls {
    live on;

    hls on;
    hls_path /usr/local/nginx/html/hls;
    hls_nested on;
    hls_fragment 5s;

    hls_variant _low BANDWIDTH=160000;
    hls_variant _mid BANDWIDTH=320000;
    hls_variant _hi  BANDWIDTH=640000;
}

しかし、自分の環境ではこのHLS化の部分がうまく動作してくれなかったので、nginxでストリームを受け取ってからHLSに流すのではなく、最初から映像ソースを送信するffmpeg側でRTMP+HLS×3の4ストリーム流すことで対応しました。 最終的に、nginx.confは以下のようになりました。

user  nginx nginx;
worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        listen       80;
        server_name  localhost;
        location / {
            root   /usr/local/nginx/html;
            index  index.html index.htm;
            expires -1;
            sendfile off;
        }
        location /hls {
            types {
                application/vnd.apple.mpegurl m3u8;
            }
            root /usr/local/nginx/html;
            add_header Cache-Control no-cache;
            # To avoid issues with cross-domain HTTP requests (e.g. during development)
            add_header Access-Control-Allow-Origin *;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
    include servers/*;
}
rtmp {
    server {
        listen       1935;
        application live {
            live on;
            wait_video on;
        }
        application hls {
            live on;

            hls on;
            hls_path /usr/local/nginx/html/hls;
            hls_nested on;
            hls_fragment 5s;

            hls_variant _low BANDWIDTH=160000;
            hls_variant _mid BANDWIDTH=320000;
            hls_variant _hi  BANDWIDTH=640000;
        }
    }
}

参考

コンテナ2の作成(映像ソース)

要件

  • 映像ソースを無限ループで配信できること。これで擬似的にLIVE配信とみなす。
  • RTMP用とHLS用とで4ストリーム配信できること。コンテナ1の設定よりffmpeg側で4ストリーム流すことが必須。

このあたり↓を参考に設定させていただきました。 テスト用の自動配信環境をDockerとFFmpegで構築してみる というかDockerfileに関しては全く同じです(汗)。

FROM jrottenberg/ffmpeg:3.2-centos

COPY source.mp4 ./
COPY start.sh ./

COPY ./ffserver.conf /etc/ffserver.conf
EXPOSE 80 1935
ENTRYPOINT bash start.sh

ただし、前述のようにffmpeg側で4ストリーム流す必要があったので、実行するシェルスクリプトを以下のように書き換えました。

#!/bin/bash

ffmpeg -re -stream_loop -1 -i 'source.mp4' 
     -acodec libfdk_aac -vcodec libx264 -f flv "${TARGET_RTMP_IP}/live/{設定したストリーム名}"  \
     -c:a libfdk_aac -b:a 32k -c:v libx264 -b:v 128K -f flv "${TARGET_RTMP_IP}/hls/{設定したストリーム名}_low" \
     -c:a libfdk_aac -b:a 64k  -c:v libx264 -b:v 256k -f flv "${TARGET_RTMP_IP}/hls/{設定したストリーム名}_mid" \
     -c:a libfdk_aac -b:a 128k -c:v libx264 -b:v 512K -f flv "${TARGET_RTMP_IP}/hls/{設定したストリーム名}_hi"

この状態でDockerコンテナを立ち上げれば映像ソースを以下の4つに対して延々と配信してくれるはずです。

${TARGET_RTMP_IP}/live/{設定したストリーム名}
${TARGET_RTMP_IP}/hls/{設定したストリーム名}_low
${TARGET_RTMP_IP}/hls/{設定したストリーム名}_mid
${TARGET_RTMP_IP}/hls/{設定したストリーム名}_hi

docker-composeでの立ち上げ

ffmpeg(コンテナ2)からnginx(コンテナ1)に映像ストリームを送る際にはコンテナ間通信となるため、コンテナをリンクさせる必要がある(??Dockerまだあまり詳しくないので多分です。。。)ので、docker-composeを使ってコンテナをリンクさせて立ち上げるようにします。

結果的に以下のようなdocker-compose.ymlとなりました。

version: '2'
services:
  nginx:
    build:
      context: ./
      dockerfile: ./Dockerfile_nginx
    container_name: nginx
    ports:
      - "80:80"
      - "1935:1935"
    volumes:
      - ./:/usr/local/nginx/html

  ffmpeg:
    build:
      context: ./
      dockerfile: ./Dockerfile_ffmpeg
    container_name: ffmpeg
    environment:
      - TARGET_RTMP_IP=rtmp://{Dockerが動作するゲストOSのIPアドレス}:1935
    links:
      - nginx

TARGET_RTMP_IPとして映像ソースの送信先環境変数として渡しています。 コンテナ2で記述したシェルスクリプトがこの宛先に向けて映像を送ります。 {Dockerが動作するゲストOSのIPアドレス}については、ゲストOSのIPアドレスなので、次項で設定するVagrant上のCentOSIPアドレスを記載します。

参考

ゲストOSの立ち上げ

VagrantCentOSを立ち上げる。

要件

  • LAN内の外部端末から接続できるような基盤とすること
  • 立ち上げる際にDockerも自動で実行できること

ここまでで、メインの機能となるDockerコンテナは作成できました。 そしていよいよそのDockerの土台となるゲストOSを立ち上げてきます。

Dockerのコンテナに別の端末から接続する際には、そのDockerが動作しているOSのIPアドレスを指定すれば可能なようです。 Docker の基本学習 ~ コンテナ間のリンク

また、Vagrantで立ち上げる仮想OSも以下の記述を加えるだけで簡単に外部からのアクセスが可能になるようです。

config.vm.network "public_network", :ip => "{IPアドレス}" , :bridge => "en0: Wi-Fi (AirPort)"

[Vagrant] LAN内の別マシンから仮想マシンにアクセスする

よって、Vagrantで立ち上げるOSに上記の設定を加えれば、その上で動作しているDockerコンテナのサーバにも外部から接続ができるようになるということです。

更に、Vagrantで仮想OSを立ち上げる際に、同時にDocker、docker-composeも実行したいです。

dockerをVagrantのprovisionerとして使う方法は公式に記述してあります。 DOCKER PROVISIONER

また、docker-composeをprovisionerとして使うためのプラグインもあり、

$ vagrant plugin install vagrant-docker-compose

でインストールできるようです。 leighmcculloch/vagrant-docker-compose

設定

結果的に以下のようなVagrantfileになりました。

Vagrant.configure("2") do |config|
  config.vm.box = "bento/centos-7.2"
  # LAN内部に公開するため、public_networkを選択
  # IPアドレスはホストOSと同じセグメントのIPアドレスでないと接続できない
  config.vm.network "public_network", :ip => "192.168.0.100" , :bridge => "en0: Wi-Fi (AirPort)"
  config.vm.synced_folder "./", "/vagrant", type: "virtualbox"
  # provisionerとしてdockerとdocker-composeが使えるようにする
  # 起動のたびにコンテナを起動
  config.vm.provision :docker
  config.vm.provision :docker_compose, yml: "/vagrant/docker-compose.yml", run: "always"
end

参考

プレイヤーを作る

今回はFlowPlayerの検証のために環境を作ることが目的だったので、FlowPlayerのセットアップ方法に従いindexhtmlを作成しました。

まずは、flowplayerのライブラリを読み込みます。

<head>
  <!-- flow player -->
  <link rel="stylesheet" href="//releases.flowplayer.org/6.0.5/skin/minimalist.css">
  <script src="//code.jquery.com/jquery-1.12.4.min.js"></script>
  <script src="//releases.flowplayer.org/6.0.5/flowplayer.min.js"></script>

今回はFlowPlayer上でHLSも使いたいので、hls.jsも読み込みます。

  <!-- load the Flowplayer hlsjs engine, including hls.js -->
  <script src="//releases.flowplayer.org/hlsjs/flowplayer.hlsjs.min.js"></script>
  <!-- optional: the quality selector plugin for quality selection when hlsjs is not supported -->
  <script src="//releases.flowplayer.org/quality-selector/flowplayer.quality-selector.min.js"></script>
</head>

プレイヤーを作成するには、<div>要素に対して、jsでソースを指定します。 RTMPは下記のように指定。

var rtmp_player_flv = document.getElementById("rtmp_player_flv");
flowplayer(rtmp_player_flv, {
  autoplay : true,
  clip: {
    sources: [
      {
        type: "video/flash",
        rtmpt : false,
        src:  "rtmp://{配信サーバのIPアドレス}:1935/live/mdst"
      },
    ]
  }
});

HLSは下記のように指定。

var hls_player = document.getElementById("hls_player");
flowplayer(hls_player,{
  autoplay : true,
  live : true,
  clip: {
    sources: [
      {
        type: "application/x-mpegurl",
        src:  "http://{配信サーバのIPアドレス}/hls/mdst.m3u8"
      },
    ]
  },
});

最終的にindex.htmlは以下のような形になりました。

<!DOCTYPE html>
<html lang="en" class="">
<head>
  <!-- flow player -->
  <link rel="stylesheet" href="//releases.flowplayer.org/6.0.5/skin/minimalist.css">
  <script src="//code.jquery.com/jquery-1.12.4.min.js"></script>
  <script src="//releases.flowplayer.org/6.0.5/flowplayer.min.js"></script>
  <!-- load the Flowplayer hlsjs engine, including hls.js -->
  <script src="//releases.flowplayer.org/hlsjs/flowplayer.hlsjs.min.js"></script>
  <!-- optional: the quality selector plugin for quality selection when hlsjs is not supported -->
  <script src="//releases.flowplayer.org/quality-selector/flowplayer.quality-selector.min.js"></script>
</head>
<body>
  <div>
    <h2>RTMP</h2>
    <div id="rtmp_player_flv"></div>
  </div>
  <div>
    <h2>HTTP Live Streaming</h2>
    <div id="hls_player"></div>
  </div>
  <p id="vinfo" class="info">&nbsp;</p>

  <script type="text/javascript">
    flowplayer(function (api) {
      api.on("load", function (e, api, video) {
        $("#vinfo").text(api.engine.engineName + " engine playing " + video.type);
      });
    });
    var rtmp_player_flv = document.getElementById("rtmp_player_flv");
    flowplayer(rtmp_player_flv, {
      autoplay : true,
      clip: {
        sources: [
          {
            type: "video/flash",
            rtmpt : false,
            src:  "rtmp://{配信サーバのIPアドレス}:1935/live/mdst"
          },
        ]
      }
    });
    var hls_player = document.getElementById("hls_player");
    flowplayer(hls_player,{
      autoplay : true,
      live : true,
      clip: {
        sources: [
          {
            type: "application/x-mpegurl",
            src:  "http://{配信サーバのIPアドレス}/hls/mdst.m3u8"
          },
        ]
      },
    });
  </script>
</body>
</html>

起動

起動する際は、Vagrantfileがあるディレクトリで下記のコマンドを実行します。

$ vagrant plugin install vagrant-docker-compose
$ vagrant up

まとめ

これにて、コマンド一つ(実際には2つ)で動画配信と視聴の環境を構築することができました。 あとはindex.htmlを修正することで様々なプレイヤーの検証を行うことができます。

応用情報技術者に2週間で合格できた背景と方法

今年の10月に応用情報技術者試験を受験しました。

先日12月16日に合格発表があり、無事合格していました。

8月に試験を申し込んだあと、そのことを忘れていたので試験勉強を始めたのは10月に入ってからでした。。。。
実質2週間程度しか勉強時間はなかったのですが、なんとか合格することができました。

今回はその経緯と勉強方法について書きたいと思います。

なぜ応用情報技術者試験を受験したか

理由としては以下の3つです。
  • 昨年、基本情報技術者試験を受験してから一年が経っていたため、次の資格を取得したかった
  • 通信系の会社にいるが、情報系の学部を卒業していないので、体系的に学ぶために資格勉強を活用したかった
  • 来年にでもネットワークスペシャリストを取得したいが、その前段階として応用を取得しておきたかった 
 
応用技術者試験の概要

応用情報技術者試験の出題形式としては、以下のように午前と午後に別れています。
午前 午後
出題形式 多肢選択式(四肢択一) 記述式
出題数
解答数
出題数:80問
解答数:80問
出題数:11問
解答数:5問
引用:IPA 独立行政法人 情報処理推進機構:制度の概要:応用情報技術者試験

合格基準は午前試験、午後試験でそれぞれ60点/100点を取得することです。

午前試験で60点が取得できていない場合、午後試験については採点すらも行われません。

試験範囲としては、以下の範囲が出題されます。
  1. 基礎理論
  2. アルゴリズムとプログラミング
  3. コンピュータ構成要素
  4. システム構成要素
  5. ソフトウェア
  6. ハードウェア
  7. ヒューマンインターフェース
  8. マルチメディア
  9. データベース
  10. ネットワーク
  11. セキュリティ
  12. システム開発技術
  13. ソフトウェア開発管理技術
  14. プロジェクトマネジメント
  15. サービスマネジメント
  16. システム監査
  17. システム戦略
  18. システム企画
  19. 経営戦略マネジメント
  20. 技術戦略マネジメント
  21. ビジネスインダストリ
  22. 企業活動
  23. 法務
もう、広すぎてなんの試験だかわかりません。笑

特に16以降なんかは、正直なんか別の人が考えてよ・・・と言いたくなってしまう気がします。。。

とはいえ、応用情報技術者資格を持つ人材に対して期待されていることは、 
基本戦略立案又はITソリューション・製品・サービスを実現する業務に従事し、独力で次のいずれかの役割を果たす。

 (1) 需要者(企業経営、社会システム)が直面する課題に対して、情報技術を活用した戦略を立案する。
 (2) システムの設計・開発を行い、又は汎用製品の最適組合せ(インテグレーション)によって、信頼性・生産性の高いシステムを構築する。また、その安定的な運用サービスを実現する。 

なので、これだけの範囲を学習しなきゃならないのも仕方ないかもしれません。

もちろん、これらの範囲を専門家並に理解しなくても問題ないのでご安心ください。

自分のもともとの学習レベル

先に、「学習時間が2週間程度しかなかった」と書いたのですが、もちろん完全にゼロからのスタートで2週間で合格したわけではありません。
こうしたことがあるので、
  • プログラム、データベース、ネットワーク周りはある程度理解している。
  • 法律的な部分であったり、CPUの構成がなんたらとかという部分は、一度ある程度勉強したことがある 
概ね上記のような状態からのスタートだったかと思います。
 
勉強方法

前述のような状態からのスタート、時間も限られていたのでひたすら過去問を解くという戦略を取りました。

午後試験は4問選択だったので、プログラミング、データベース、ネットワークを選択すればまぁ解けるだろうという甘い考えでした。

それ以上に、午前試験で60点以上を取得しないと無条件で不合格になってしまうので、そちらの対策に全力を注いだわけです。

毎日こちら↓のサイトを使って過去問を解き、5年分は解きました。
応用情報技術者試験ドットコム

また、下記の本も購入しわからないことは調べて、理解を深めました。



まぁ、一応本は買いましたが、正直使用頻度も少なかったですし、買わなくても大丈夫だったと思います。

こうしてひたすら過去問を解いたことで、概ね出題されるパターンも分かってきて、よく出る知識については効率よく覚えることができました。

午後試験の選択問題もすんなりと解くことができ、無事合格することができました。

まとめ

応用情報技術者試験は上述の通り、試験範囲も広いですし、午後試験も基本情報技術者試験に比べ記述式の分量が多く、とても大変な試験に見えるかもしれませんが、受けてみるとさらっと合格することができました。


その要因としてはやはり、ある程度技術的な知識を持っていたということが大きいと思います。
そういう意味では、もともと技術職の方であったり、そういった勉強をしている方なら、とてもかんたんに取ることができる資格なので、範囲の広さに怯える必要は無いと思います。


逆に技術にあまり詳しくない方は、午前試験に加えて午後試験の記述対策もしっかりと行う必要があるので、結構たいへんかもしれません。