AWSを契約してVPCとか仮想サーバとか試してみました。かたりぃなです。
AWSのアカウント作成
登録はクレジットカードが必要になります。
ユーザー登録自体は無料なのですが、無料登録枠からはみ出して課金となった場合、この登録しているクレジットカードから引き落としされるようなので注意が必要です。
(まだ実際に課金は発生していないので、具体的にどうなるのかは見たことありません。)
無料利用枠
新規登録から一年間は、無料利用枠というものがあります。
たとえば基本的なクラウドサーバであるEC2の最小構成であれば、無料利用枠で利用できたりします。
しかし残念ながらDockerコンテナをデプロイできるサービスは無料利用枠には含まれていないようです。 (Elastic Container Service = ECS)
なので、今回はまずEC2を利用します。
AWSの初期設定
セキュリティを考慮して構成を考えると、次のような構成になるかと思います。
どうしてこうなるかというと、どこからでもSSHでログインできるインスタンスというのはなかなか怖いものがあります。(AWSの管理コンソールでも警告が出ています。)
1でやりたいことは、管理用インスタンスは専用の固定IPからのみアクセス可能としておいて、他からのアクセスは禁止します。
2はパブリックIPを割り当てずにVPC(AWS上の自分のLANとでも思ってください)内からアクセスするものとします。 これはSSHの経路です。 HTTPで外から来る人向けにロードバランサ経由でプライベートIPへと転送させます。
これでなかなかよさげな構成(一般的なのかな)になりますが、複数インスタンスが必要だったりして、それなりにお金がかかってきてしまいます。 なので、簡易的な対処でいったんEC2を立てて、実験してみます。
SSHのポート番号を変えておくというのは、なかなか原始的な手法ですが、何もないよりは安心といったところですね。
自分がアタックする立場なら、22番とりあえず叩いてみるとかするでしょうし、それなりに効果はあるらしいです。
AWS-EC2の設定
AWSの操作はもっと有用な情報源があるので、ここでは省略します。 無料利用枠のデフォルト値から変更した箇所のみ記載します。
セキュリティグループ - HTTPあけた, SSHポート番号を変えた ディスクサイズ 30GiB。無料利用枠いっぱい
Linuxマシンの設定
まずSSHポートの設定を変更します。
/etc/ssh/sshd_config の上のほうに Port 22 という表記があるので、これを変えてsshdを再起動します。
sudo service sshd reload
ローカルマシンの設定(Windows)
今回はWSLのsshを使ってAWSに接続します。 AWSインスタンスを作成するときにキーペアがないとアクセスできなくなるよ?みたいに言われたかと思います。 この鍵をローカルマシンに保存します。
必要に応じて.ssh/configとか書いてアクセスできればOKです。 今回はこんな感じで書きました
Host aws-ec2 HostName パブリックIPアドレス IdentityFile ~/.ssh/aws-first-keypair.pem User ec2-user Port ポート番号
なお、ここで指定した鍵はパーミッション設定間違えやすいです。(Windows側からコピーして持ってくると、実行権限がついてたりとか)
chmox 600 ですかね。とりあえずは。
ansible
ansibleはプロビジョニングツールと呼ばれるもので、コマンドを実行することで対象のマシンに必要な設定をしてまわる便利なやつです。 今回はこれを使ってみます。
ansibleの追加パッケージを使うとEC2をたてたりする機能も使えるようになるらしいですが、はまだ試していないのであしからず。
さて、EC2でこれを使う目的は、いくつかありますが、代表的なものは以下の2つかと思います。
- マシンの環境を再現しやすくすること
- 複数台マシンにスケールアウトする場合のプロビジョニングの手間の削減
1はよくある「ライブラリ依存の問題」の解消などがわかりやすいと思います。 昔のWindowsだとよくあったトラブルで「なんかよくわからないけどアップデート走ったら治った」みたいなやつです。
趣味で適当にやるだけならいいですが、お客さんに納品するシステムなんかで「よくわかりませんが治りました」って嫌ですよね。(少なくとも私はそういう業者は信用しません)
ここではライブラリの組み合わせなど、環境を固定できるのが大きなメリットです。
手作業でやってたりオレオレ.shを自作したりすると後で見たとき泣きそうになりますが、ansibleで一定のフォーマットが担保されるなら少しは安心かなと思います。
2はスケールアウトを考えたとき、複数台に同じ処理を流していくのって手間かかりますし、ミスが紛れ込みます。 たとえばhost1にコマンド打ち込んだつもりが、host2に打ち込んでいたとか。 そういうミスを減らすためにもこういうツールはどんどん使っていくべきだと思います。
Ansible実行環境
WindowsではAnsibleが走ってくれません。Linux由来のパッケージではよくあることなので、あきらめて「ansibleを実行するためのdockerコンテナ」をたてます。
FROM centos RUN yum -y install initscripts MAKEDEV # 開発ツール RUN yum groupinstall -y 'Development tools' RUN yum install -y git wget cmake RUN yum install -y sudo RUN yum install -y ansible # 文字コード #RUN yum -y reinstall glibc-common RUN localedef -v -c -i ja_JP -f UTF-8 ja_JP.UTF-8; echo ""; env LANG=ja_JP.UTF-8 RUN rm -f /etc/localtime RUN ln -fs /usr/share/zoneinfo/Asia/Tokyo /etc/localtime # python 3 RUN yum install -y https://centos7.iuscommunity.org/ius-release.rpm RUN yum install -y python36u python36u-libs python36u-devel python36u-pip RUN pip3.6 install --upgrade pip RUN pip3.6 install boto # dockerコンテナとホスト間での共有ディレクトリ RUN mkdir /tmp/data -p # 共有ディレクトリの準備 RUN mkdir -p /tmp/data # 起動オプション。CMDに指定できるのは一つだけ。 EXPOSE 22 CMD bash -c "/usr/sbin/sshd -D"
sshを許している理由は、docker内に鍵を置きたくないからです。事故らせる自信あります。(ぇー)
Windows側からsshするとき-Aでフォワードかけて、その鍵を使ってAWSに接続する。という使い道を想定しています。
playbookをかく
playbookの詳細はもっと有用なサイトに譲るとして、やってみた内容だけ書いていきます。
まずplaybook本体です。
開発ツール、C++用のライブラリ(boost, opencv)、Python用ライブラリ(chainer, chainerUI)をインストールすることを記載します。
--- - name: hosts: all user: root become: yes become_user: root roles: - devtools - cpp-libs - chainer
cpp-libsロールのmain.ymlでは、opencvとboostをインストールしたいので、それぞれわけて書きました。
--- - include: tasks/opencv.yml - include: tasks/boost.yml
opencvのインストールはこんな感じで書いてます。 git cloneしてcmakeしてビルドですね。
ちょっと古いdockerfileからコピってきたのでバージョン古いままです。
- name: Install opencv git: repo: 'https://github.com/opencv/opencv.git' dest: '/root/opencv/' version: 3.3.1 - name: Install opencv_contrib git: repo: 'https://github.com/opencv/opencv_contrib.git' dest: '/root/opencv_contrib' version: 3.3.1 - name: make opencv build dir file: path: /root/opencv/build/ state: directory mode: 0755 - name: check cmake configure stat: path: /root/opencv/build/CMakeCache.txt register: cmake_cache - name: configure opencv shell: > cd /root/opencv/build; cmake -DOPENCV_EXTRA_MODULES_PATH=/root/opencv_contrib/modules ..; when: not cmake_cache.stat.exists
boost側は似たようなもんなので省略します。
ansible-playbookの実行
ここまでで一通りのplaybookができました。 こんな感じで実行します。
ansible-playbook -i inventory/hosts playbook.yml
playbookのトラブルシューティング
Opencv, boostとも大きなライブラリなので、結構容量食います。 この大きさ故にEC2のT2-microのデフォルト設定では容量不足で終了します。
T2-microインスタンスののデフォルトストレージ容量は8GByteしかありません。 AWS無料利用枠の上限として合計30GiByteまでいけるようなので、おとなしくインスタンス作り直し、大きめの容量をとってリトライすればうまくいきました。
ちなみに、AWSのストレージ容量表記はSI単位系ではなく二進接頭辞(「キビバイト(KiB)」とか)になっていました。 まあサーバ系でギリギリの容量で運用することは普通やらない(組み込みソフトならいざ知らず…)ので、あまり気にしなくてもいいかもしれませんが、頭の片隅に置いておきましょう。
起動、動作確認
以前のブログ記事でやったソースコードをアップロードして、ビルド、実行です。
つまり、PNGファイルをHTTP-POSTしてあげれば、画像解析をして結果をJSONで返してくれるわけです。
実験用クライアントアプリ
最終的にHololensからアクセスする予定のRESTなので、Hololensと同様のUWPで作ります。 少しずつですが慣れてきたC++/CXです。
XAMLにボタンとか適当において、イベントハンドラに次のコードを記述します。
FileOpenPicker^ openPicker = ref new FileOpenPicker(); openPicker->ViewMode = PickerViewMode::Thumbnail; openPicker->SuggestedStartLocation = PickerLocationId::PicturesLibrary; openPicker->FileTypeFilter->Append(".jpg"); openPicker->FileTypeFilter->Append(".jpeg"); openPicker->FileTypeFilter->Append(".png"); create_task(openPicker->PickSingleFileAsync()).then([this](StorageFile^ file){ if (file) { // ファイルが選択された // todo : キャンセル処理が必要。 } return file->OpenReadAsync(); }).then([](Windows::Storage::Streams::IRandomAccessStream ^ stream) { // こうすればPOSTするためのHTTPリクエスト作れるらしい auto uri = ref new Windows::Foundation::Uri(L"http://EC2のパブリックIP/"); auto streamContent = ref new Windows::Web::Http::HttpStreamContent(stream); auto request = ref new Windows::Web::Http::HttpRequestMessage(Windows::Web::Http::HttpMethod::Post, uri); request->Content = streamContent; auto filter = ref new Windows::Web::Http::Filters::HttpBaseProtocolFilter(); filter->CacheControl->ReadBehavior = Windows::Web::Http::Filters::HttpCacheReadBehavior::MostRecent; auto client = ref new Windows::Web::Http::HttpClient(filter); auto headers = client->DefaultRequestHeaders; return create_task(client->SendRequestAsync(request)); }).then([this](task<Windows::Web::Http::HttpResponseMessage ^ > previousTask) { try { auto response = previousTask.get(); // ここではHTTPリクエストの動作確認が目的なので、ログに出して終わる。 auto body = response->Content->ToString()->Data(); auto header = response->Headers->ToString()->Data(); OutputDebugString(header); OutputDebugString(body); } catch (const task_canceled&) { // HTTPリクエストがキャンセルされた場合はここに来る OutputDebugString(L"http request task chanceld\n"); } catch (Exception^ ex) { // HTTPリクエストで何らかのエラーが発生した場合はここ OutputDebugString(L"http request task error\n"); } });
やっていることは次のとおりです。
- イメージピッカーを表示して、画像ファイル選択のUIを表示します
- 選択された画像ファイルを読みだし用に開き、ストリームを得ます。
- HTTPクライアントのリクエストにストリームを関連付け、リクエストを開始します
- HTTPレスポンスをログに出力します。
この手順の3番目以降はHololensに移植してもそのまま使える部分ですね。
つまり、リクエストに載せるストリームをカメラ映像由来のものにすると。
できた!
AWSのサーバにリクエストを投げて、レスポンスを受け取るという基本的な部分ができました。
動作結果は以前に Hololens - デスクトップPCでやったのと同じです。
まだサーバ側に認証・認可はつけてないので、不用意にアクセスされないよう、使わないときは落としておく運用とします。
感想と今後の展望
プロトタイプとはいえ段々とシステムを作っている感じが出てきました。
まだまだやることあ多いですが、少しずつ自分のペースでやっていきたいと思います。
次はHololens側との連携になりますが、サーバ側の画像検出器が少々不安定で落ちることがあるので、修正していくことになるかなと思っています。
こういうときに今回作ったようなダミーモジュールがあると便利ですね。
それでは今回はこれくらいで。