Raspberry pi 3で暗視カメラシステムの構築 その3
時間が空きましたが、前回の続きです。今回はカメラから画像を送信する処理と画像を受け取って保存しておき、要求に応じて画像を表示するhtmlを返すwebサーバーを切り分けた部分の紹介になります。ここまでで、下図の設計の左半分が出来ました。こうすることで画像を送信するRaspberr pi3とwebサーバーを別々にします。
Raspberry Pi3側画像送信スクリプト
ソースコードは以下になります。
import tornado.websocket import asyncio import tornado.ioloop import pickle import time from capture_video import CaptureVideo async def main(): url = 'ws://192.168.100.50:8888/camera?mode=send_frame' conn = await tornado.websocket.websocket_connect(url) # 画像データ送信 await send_frame_loop(conn) conn.close() async def send_frame_loop(conn): try: while True: ret, frame = CaptureVideo.get_frame() if ret: binary_image = pickle.dumps(frame) conn.write_message(binary_image, binary=True) await asyncio.sleep(0.1) except KeyboardInterrupt: conn.close() CaptureVideo.capture_start() ioloop = tornado.ioloop.IOLoop.current() ioloop.stop() ioloop.close() if __name__ == '__main__': CaptureVideo.capture_start() tornado.ioloop.IOLoop.current().run_sync(main)
webサーバー側をtornadoで実装しているため、WebSocketクライアントもtornadoを利用しました。WebSocketサーバーに接続するときに、クエリストリングでパラメーター'send_frame'を渡して、画像を送信することをサーバー側に伝えています。あとはsend_frame_loop関数で非同期定期的に画像を送り続けます。前回まででwebサーバー側でカメラから直接画像を取得していた部分を、こちらに置き換えています。画像を取得するクラスCaptureVideoに関しては前回の記事をご覧ください。
Webサーバー
WebSocketサーバーの変更
さて、Raspberr Pi3側で画像を送り、別のマシンに配置したWebサーバーで画像を受け取る構成になったため、サーバー側も変更する必要があります。ソースコードが以下になります。
import cv2 import time import asyncio import tornado import tornado.httpserver import tornado.websocket import tornado.ioloop import tornado.web import pickle from capture_video import CaptureVideo from frame_data import FrameData class HttpHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def initialize(self): pass def get(self): self.render('./index.html') class WSHandler(tornado.websocket.WebSocketHandler): def open(self, args): print(self.request.remote_ip, ": connection opened") self.mode = self.get_query_argument('mode') print(self.mode) if self.mode == 'get_frame': self.ioloop = tornado.ioloop.IOLoop.current() self.loop() def on_close(self): print("Session closed") self.close() def check_origin(self, origin): return True def on_message(self, message): if self.mode == 'send_frame': frame = pickle.loads(message) frame_data = FrameData.get_instance() frame_data.set_frame(frame) def loop(self): self.ioloop.add_timeout(time.time() + 0.1, self.loop) frame_data = FrameData.get_instance() encode_param = [int(cv2.IMWRITE_JPEG_QUALITY),90] ret, decimg = cv2.imencode('.jpg', frame_data.get_frame(), encode_param) if self.ws_connection: if ret: message = decimg.tobytes() self.write_message(message, binary=True) def main(): try: app = tornado.web.Application([ (r'/', HttpHandler), (r'/camera(.*)', WSHandler), ]) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(8888) print('server start') io_loop = tornado.ioloop.IOLoop.current() io_loop.start() except KeyboardInterrupt: print('server stop') ioloop = tornado.ioloop.IOLoop.current() ioloop.stop() ioloop.close() if __name__=='__main__': main()
主な変更はWebSocketサーバーの部分です。WebSocketの通信を画像を受け取る場合と、画像をhttpクライアント側に送信する場合で処理を分けました。接続を開始するOpen関数でGetリクエストでクエリストリングを受け取るようにし、受け取ったパラメータが'send_frame'なら画像を受け取って保存、'get_frame'なら今まで通り画像を送信してhtmlで表示するという流れです。
これに合わせてサーバーが返すindex.htmlも少しだけ変更しています。クライアントで実行されるWebSocketに接続するjavascriptの部分で、接続するときにパラメータ'get_frame'を送るようにしました。
var ws = new WebSocket("ws://192.168.100.50:8888/camera?mode=get_frame");
Singletonで画像データの共有
また、カメラから受け取った画像は保存しておいてhttpクライアントに送らないといけないため、画像データを保存しておくためのSingletonクラスFrameDataを作成しました。Singletonはオブジェクト指向プログラミングのデザインパターンの一つで、プログラム内で、「あるクラスのインスタンスを1つしか生成しない」という仕組みを実装するものです。コードは以下です。
from threading import Lock class FrameData: # Singleton _unique_instance = None _lock = Lock() def __new__(cls): raise NotImplementedError('Cannot initialize via Constructor') def set_frame(self, frame): self._unique_instance.frame = frame def get_frame(self): if self._unique_instance.frame is not None: return self._unique_instance.frame else: print('Frame is None') return None @classmethod def __internal_new__(cls): return super().__new__(cls) @classmethod def get_instance(cls): if not cls._unique_instance: with cls._lock: if not cls._unique_instance: cls._unique_instance = cls.__internal_new__() cls._unique_instance.frame = None return cls._unique_instance
画像を保存するときにget_instanceでインスタンスを取得し、set_frame関数で画像を保存。画像を取り出すときはget_instanceでインスタンスを取得し、get_frame関数で画像を取り出しという流れです。pythonはSingletonの仕組みは標準では実装されていないようでしたので、参考にさせてもらったwebサイトに記載されていたコードを改良して自前で用意しました。Raspberry pi3カメラで接続される場合と、httpクライアントからの接続では別のWebSocketインスタンスが生成されるため、その中で画像を共有するためにSingletonで共通のインスタンスを利用する方法を取りました。
基本的な部分は今回までで完成しました。公開サーバーへのデプロイ、画像認識やレイアウトなどを次回以降でやっていこうと思います。
参考
Pythonでシングルトン(Singleton)を実装してみる - [Dd]enzow(ill)? with DB and Python
Raspberry pi 3で暗視カメラシステムの構築 その2
前回の記事の続きになります。今回はRaspberry Pi 3 BにpythonでTronadoのWebサーバーを立てて暗視カメラで撮影した映像をWebブラウザから確認するところまでを紹介します。
Raspberry Pi上のwebサーバー
TornadoのWebServer
以下がpython3で書いたコードになります。
import cv2 import time import asyncio import tornado import tornado.httpserver import tornado.websocket import tornado.ioloop import tornado.web from capture_video import CaptureVideo def capture_frame(): CaptureVideo.capture_start() class HttpHandler(tornado.web.RequestHandler): @tornado.web.asynchronous def initialize(self): pass def get(self): self.render('./index.html') class WSHandler(tornado.websocket.WebSocketHandler): def open(self): print(self.request.remote_ip, ": connection opened") self.ioloop = tornado.ioloop.IOLoop.current() self.loop() def on_close(self): print("Session closed") self.close() def check_origin(self, origin): return True def loop(self): self.ioloop.add_timeout(time.time() + 0.1, self.loop) frame = CaptureVideo.get_frame() encode_param = [int(cv2.IMWRITE_JPEG_QUALITY),90] ret, decimg = cv2.imencode('.jpg', frame, encode_param) if self.ws_connection: if ret: message = decimg.tobytes() self.write_message(message, binary=True) def main(): try: app = tornado.web.Application([ (r'/', HttpHandler), (r'/camera', WSHandler), ]) http_server = tornado.httpserver.HTTPServer(app) http_server.listen(8888) print('server start') io_loop = tornado.ioloop.IOLoop.current() capture_frame() io_loop.start() except KeyboardInterrupt: print('server stop') ioloop = tornado.ioloop.IOLoop.current() ioloop.stop() ioloop.close() if __name__=='__main__': main()
httpでgetリクエストを待ち受けて、Webブラウザに動画表示用のhtmlファイルを返します。Webブラウザ側はWebSocketで接続して画像を取得するようにするため、WebSocketの通信を待ち受けるHandlerも実装しています。WebSocketの通信が確立されるとloopメソッドが呼び出されて画像をクライアント側に送信し続けるようにようにしました。
画像はOpenCVを利用して処理します。OepnCVの導入については前回の記事をご覧ください。WebSocketでWebブラウザに画像を送信する前にJPEG形式に変換してバイナリデータとして送信するという流れになります。
Cameraから画像キャプチャ
続いて上記のコードで利用しているCaptureVideoクラスについて説明します。このクラスはpythonのasyncioライブラリを利用してWebサーバーとは非同期にカメラから画像を取得するために作成しました。
import asyncio import cv2 class CaptureVideo: is_running = False frame = None @classmethod def capture_start(cls, camera_id=0): if not cls.is_running: cls.is_running = True asyncio.ensure_future(cls.__capture_loop(camera_id)) @classmethod def get_frame(cls): if cls.is_running: return cls.frame else: print('capture loop is not running!') @classmethod def stop_capture(cls): if cls.is_running: cls.is_running = False @classmethod async def __capture_loop(cls, camera_id): cap = cv2.VideoCapture(camera_id) try: while cls.is_running: ret, cls.frame = cap.read() await asyncio.sleep(0.1) except: loop = asyncio.get_event_loop() loop.stop() cls.isrunning = False cap.release()
__capture_loopメソッドが非同期にカメラの画像を取得し続けるメソッドです。awaitの部分で処理を中断して制御をWebサーバー側に渡しています。
詳しくはこちら。このクラスを先に記載したWebサーバーの方でimportしてio_loop.startの前にカメラから画像を取得する処理を開始し、WebSocketのループの中で必要に応じてget_frameメソッドにより画像を取得するという仕組みです。
最新のTornadoではpython3.5から導入されたこのasyncioライブラリを利用して非同期処理を行うのことを推奨しています。この辺りは日本語で説明されている記事がとても少ない印象でしたので、興味のある方は先ほどリンクを張ったTronadoの公式リファレンスを読まれるのが一番良いと思います。
また、日本語の記事ですとTornadoでWebサーバーを待ち受け開始する際に利用するメソッドであるIOLoop.current()がIOLoop.instance()になっているものが多いのです。こちらは公式で非推奨になっているメソッドで、IOLoop.current()を利用するように記載されています。
ライブ動画を表示するhtml
最後にWebサーバーから返されるhtmlファイルを説明します。
<html> <head> <title>livecamera</title> <font size="5"><b>livecamera by Raspberry Pi3</b></font><br> <img id="liveImg" src=""> <script type="text/javascript"> var img = document.getElementById("liveImg"); var ws = new WebSocket("ws://192.168.11.205:8888/camera"); ws.onopen = function(){console.log("connection was established")}; ws.onmessage = function(evt){ window.URL.revokeObjectURL(img.src) img.src = window.URL.createObjectURL(evt.data); }; window.onbeforelaod = function(){ ws.close(1000); }; </script> </head> </html>
ブラウザでWebサーバーにアクセスするとこちらのhtmlファイルが表示され、Javascriptで書かれたWebSocketに接続する部分が実行されます。あとは受け取った画像をhtml5のimgタグで表示するというものです。こちらは、参考にさせていただいたWebページのものを少し改良して使用させていただきました。
今回はここまでとなります。最初にシステムの概要図を再掲しましたが、このままだとすべてRaspberry Pi上で完結しています。そこで次回ではWebサーバーを別のPC上にたてて、そこにRaspberry Piから画像データを送信し、クライアントからの要求に対して受け取った画像データを送るというように構成を変更するところまでできればと思っています。
参考
websocketでRaspberryPiからwindowsPCに画像を送る(Python) - ロボット、電子工作、IoT、AIなどの開発記録
Tornadoを使ってブラウザをPythonの表示器代わりに使うメモ - Qiita
【ChatDeTornado】TornadoでWebSocketを使ってチャットを作る - Qiita
AjaxでバイナリのJPEG画像データを受け取って表示する - Qiita
Raspberry pi 3で暗視カメラシステムの構築 その1
実家のハムスターを見張る目的で暗視カメラシステムを構想中です。
ひとまずRaspberry pi 3 Model Bに赤外線カメラモジュールを接続して画像をwebサーバーに送信、webサーバー経由でどこからでも確認できる仕組みを検討しています。
システム構成図
思いつくままに描いてみたシステム構成が下図です。想像しながら書いた部分もあるので、問題があれば適宜変更予定。
OpenCVを使って画像認識をおこない、通知機能や録画機能の自動化をできればいいかなと考えています。Herokuの負荷や安全性については全く未調査なので、そちらもおいおい行う予定。Herokuでダメなら安いサーバー1つくらいレンタルしてもいいかな。
暗視カメラ
Amazonで購入。LED赤外線投光器付きの赤外線カメラモジュールにしました。Raspberry Piのカメラ専用端子にリボンケーブルで接続して使うタイプです。
特に問題なく接続できたのですが、ドライバをmodprobeコマンドで毎回手動で読み込まないと認識してくれなかったため「/etc/modules-load.d/moduled.conf」の末尾行に「bcm2835-v4l2」と追記して、起動時にドライバを自動で読み込むようにしました。
OpenCVの準備
python3を使用する予定なのでpythonからOpenCVを使用できるようにします。お手軽なのでpipからインストールする方法にしました。ソースコードからRaspberry pi上でbuildする方法もあります。一応Linux上でbuildしたこともあるのですが、Raspberry Piでbuildすると熱暴走などにも気を付けないといけません。OpenCVに明るい人なら、最新版を必要な機能だけ最適な状態でインストールできるので自前buildもありです。
まずはOpenCVに必要なモジュールのインストール。
sudo apt install cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev
pip3 install opencv-python
この状態でpython側からopencvをインポートするとライブラリがありませんというエラーが何度か表示されるので、さらに追加でライブラリをインストールしていきます。
エラー時に表示されるメッセージをよく読んで検索すれば必要なライブラリが判断できるので、先ほどと同様に「apt」コマンドでインストールしていきましょう。
警告が出たライブラリをメモし忘れていたので以下一例です。ご参考まで。
sudo apt install libcblas-dev libatlas3-base libilmbase12 libqtgui4 libqt4-test
テスト用にカメラから画像を取得して表示する簡単なスクリプトを動かします。
import numpy as np import cv2 cap = cv2.VideoCapture(0) while(cap.isOpened()): ret, frame = cap.read() if ret == True: cv2.imshow('frame', frame) if cv2.waitKey(1) & 0xFF == ord('q'): break else: break cap.release() cv2.destroyAllWindows()
無事に動画が表示されれば成功です。
次回へ続きます。
参考
Python3でSeleniumを利用してwebサイトから情報抽出
映画が好きでちょくちょく映画館に行くのですが、それほど頻度が多くないので見たい映画を見逃すことがありました。
鳥取県在住なので映画館はほぼ候補がなく、1つだけ確認しておけばいいのでどうせなら自動化してやろうと思いスクリプトを自作してみました。
Python3とSeleniumを使ってブラウザを自動操作し、映画館の上映情報を取得するというスクリプトの備忘録兼まとめ記事です。
Seleniumのインストール
SeleniumはプログラムからWebブラウザを操作するツールです。Webアプリ開発でテストに利用したり、Webから情報を抽出して利用するWebスクレイピングで利用されます。今回はpython3から動かして利用しました。
pipでインストールできますので導入はとても簡単です。以下コマンドでパッケージをインストールしてください。
pip install selenium
WebDriverをインストール
SeleniumでWebブラウザを操作するために必要なソフトをインストールします。今回はChromeを利用しました。2018/09/27現在最新のドライバー2.42を利用すればすべてのバージョンのChromeを操作できるようです。リンク先のDownloadsからたどっていけばダウンロードできます。
ChromeDriver - WebDriver for Chrome
インストールしたら任意のディレクトリにドライバーを配置してください。配置したパスはpythonのコードで指定するので覚えておきましょう。
Python3のソースコード
以下が作成したPython3のスクリプトになります。
「Seleniumの設定」のところで"--headless"と指定しているのは、ブラウザをウィンドウで開かないようにしています。この指定をしないとスクリプトを実行する度に、Crhomeが起動します。Seleniumの動作を確認したいときはコメントアウトすると動きが分かりやすいです。
DRIVER_PATHに先ほどインストールしたChrome用WebDriverのパスを指定し、driver変数を用意してブラウザを操作しています。URLには情報を抽出したいWebサイトのurlを指定します。
実際にブラウザを操作しているのがメソッドfind_elements_by_css_selector()のあたりです。このメソッドはWebページのCSSセレクタを指定することで、任意の要素を取得することができます。最初の呼び出しでは上映スケジュールのボタンを取得してきてクリックすることで、上映映画の一覧を表示する動作をさせています(興味のある方は指定したURLにアクセスして週間上映スケジュールボタンを押してみてください)。CSSセレクタに何を指定するかはWebページを解析して判断する必要があります。ブラウザの開発者ツールなどを利用して解析してみましょう。
for文のところで上映映画のタイトルを取得してmessageに格納し、Line Notityで通知を送っています。Line Notifyについては以前の記事で紹介しているのでよろしければそちらをご覧ください。
doinaka-it-zakki.hatenablog.com
from selenium import webdriver import line_notify as ln DRIVER_PATH = "D:/driver/chromedriver.exe" URL = "https://www.smt-cinema.com/site/hiezu/index.html" # LINE notifyによる通知設定 lne = ln.LineNotify('LINEのAPIトークン', 'https://notify-api.line.me/api/notify') # Seleniumの設定 options = webdriver.chrome.options.Options() options.add_argument("--headless") driver = webdriver.Chrome(DRIVER_PATH, chrome_options=options) driver.get(URL)
elem_schedule_btn = driver.find_elements_by_css_selector("#schedule > ul > li") if len(elem_schedule_btn) > 1: elem_schedule_btn[1].click() movie_list = driver.find_elements_by_css_selector(".weekly > div") message = '' for movie_elem in movie_list: title = movie_elem.find_elements_by_css_selector(".weekly > div > div > div > p.title") message += title[0].text + '\n' # LINEに週間上映映画情報を送信 res = lne.send_message(message)
driver.close()
driver.quit()
無事に映画の上映リストを取得して、LINEに通知を送ることが出来ました。余談ですが私は毎日12:00に通知がくるようにスクリプトの定期実行を設定しています。
参考
PythonとSeleniumを使ったブラウザ自動操作 – 名古屋のWebシステム開発 iNet Solutions
SeleniumからHeadless Chromeを使ってみた
PythonでSeleniumを使ってスクレイピング (基礎)
Ubuntu16.04でRuby on Rails5の開発環境構築
Vagrant環境のUbuntu16.04でRuby on Rails5の開発環境を整えた作業の備忘録です。
なお作業は基本的に一般ユーザーの権限で行い、必要に応じてsudoコマンドを使用しています。
環境は以下
- Ubuntu16.04
- Ruby2.5.1
- Rails5.2.1
Ubuntuデスクトップ環境の日本語化
キーボードをJIS配列に変更
Vagrantではubuntu/xenial64のboxを利用しましたが、日本語化されているないのでまずは日本語化を行いました。導入の方法は前回の記事を参照ください。
以下のコマンドを実行してキーボードをJIS配列に変更します。
$ sudo dpkg-reconfigure keyboard-configuration
下のような画面になるのでJIS配列になるように選択していきます。
- Generic 105-key (Intl) PC
- Japanese
- Japanese
- The default for the keyboard layout
- No compose key
- NO
次に右上の歯車ボタンからSystem Settingsを選択して、検索からText Entryの設定に入ります。
Input sources to useの欄の左下にある+-ボタンを操作してJapaneseだけになるようにします。下の画像は各種設定終了後のためMozcも追加されていますが無視してください。また、Switch to next source using:のボックスを選択して「半角/全角」ボタンを入力してください。これで、半角/全角ボタンで英字入力と日本語入力を切り替えられるようになります。
日本語化
デスクトップの日本語化対応を行います。
$ sudo apt-get install language-pack-ja-base language-pack-ja $ localectl set-locale LANG=ja_JP.UTF-8 LANGUAGE="ja_JP:ja" $ source /etc/default/locale
続いて、先ほどと同じSystem SettingsかたLanguage Supportを選択します。
LanguageのタブのLanguage for menus and windowsで日本語を一番上に持っていきます。ログインし直すか、再起動すればデスクトップが日本語化されます。
日本語入力対応
日本語入力のためのインプットフレームワークパッケージをインストールします。
$ sudo apt-get install fcitx fcitx-mozc
インストールが終わったら先ほどのLanguage Supportの設定からキーボード入力に使うIMシステムをfcitxに変更してください。
もう一度ログインしなおしてください。
ログインしたら、右上のキーボードのマークをクリックして「現在の入力メソッドの設定」を選択します。設定画面が開いたら、Input Methodから「キーボード-日本語」「Mozc」の二つを+ボタンを押して追加します。以下の画像のようになっていればよいです。
Rails5の導入
Rubyの準備
まずはRubyのバージョン管理ができるrbenvをインストールします。だたし、aptの標準リポジトリだとrbenvのバージョンが古いためruby2.3までしかインストールできません。そこでgitから直接rbenvを導入します。rbenvとrubyをインストールするためのruby-buildをgitリポジトリからcloneしてきます。
$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
$ git clone https://github.com/sstephenson/ruby-build.git .rbenv/plugins/ruby-build
続いて、.bashrcに必要な記述を追加します。sourceコマンドで現在のシェルで記述を有効化します。.bashrcはシェルを起動するたびに読み込まれるファイルです。以降はターミナルを開くたびに自動的に読み込まれるのでsourceコマンドは必要ありません。
$ echo 'export PATH="~/.rbenv/bin:$PATH"' >> ~/.bashrc $ echo 'eval "$(rbenv init -)"' >> ~/.bashrc $ source ~/.bashrc
これでrubyをインストールしてversion管理する準備が整ったので、必要なversionのインストールを行います。今回はruby 2.5.1です。rbenvでインストール可能なrubyのversionは「rbenv install -l」で確認することができます。
$ rbenv install -l
$ rbenv install 2.5.1
$ rbenv global 2.5.1
今回は2.5.1をインストールしましたが、rbenvを利用しているため必要に応じてversionを切り替えることも可能です。その都度インストールすればよいです。最後のglobalで現在使用するrubyのversionを指定しています。globalをlocalにすることでカレントディレクトリで利用するversionの指定もできるようです。
Railsの導入
最初に、rubyのパッケージ(gem)依存性管理を行うbundlerをインストールします。
$ gem install bundler
続いてrailsの開発環境をテストするディレクトリを作成して、初期化します。本開発を行う場合プロジェクト名などをディレクトリ名にすればよいと思います。
$ mkdir ~/rails-test $ cd ~/rails-test $ bundle init
Gemfileが作成されるので、ここにrailsを追加します。Gemfileは必要なパッケージを記述しておくことで簡単にパッケージをインストールするためのファイルです。最終行がrailsのパッケージ追加を指定する記述です。
# frozen_string_literal: true source "https://rubygems.org" git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } gem "rails", '~> 5.0'
準備ができたのでプロジェクトのディレクトリにrailをインストールします。Gemfileを読み込んでパッケージをインストールするためにbundleを利用します。プロジェクトにだけパッケージを導入するためににインストール先の指定をvendor/bundleを指定します。この指定を行わない場合、現在使用しているruby環境すべてにパッケージがインストールされてしまいます。1度指定しておけば、同じディレクトリでは次回以降は自動で指定されます。
$ bundle install --path vendor/bundle
$ bundle exec rails new --skip-bundle rails-app
サブディレクトリに必要なパッケージをインストールします。
$ cd rails-app $ bundle install --path vendor/bundle
railsを起動します。
$ bundle exec rails server
起動出来たらwebブラウザからアクセスしてみましょう。アドレスバーに「localhost:3000」と入力します。以下の画面が表示されたら成功です。
以上で終了です。
参考
WindowsにVagrantとVirtualBoxを使ったUbuntu GUI開発環境を構築する
仮想化ツールを管理するVagrantでUbuntu16.04を導入したお話
今回は仮想化ツールのラッパーであるVagrantを利用して、仮想化ソフトVirtualBoxを用いてUbuntu16.04を導入する方法を備忘録兼ねてまとめたいと思います。
環境は以下です。
- Windows10 Pro
- VirtualBox 5.2.16
- Vagrant 2.1.2
Vagrantの導入手順
Vagrantについて
VagrantはVirtualBox,VMware等の仮想化ソフトを管理して仮想環境の導入時の設定や、開発環境の共有等を簡潔にしてくれる仮想化ソフトのラッパーツールです。以前から存在を知っていたのですが、私はVirtualBox単体で利用していました。先日、プライベートでRuby on Rails5の開発環境が必要になり、Linuxで環境構築を行いたかったため、これを機にVagrantを利用することにしました。
ViartualBoxとVagrantのダウンロード
まずは公式ページからそれぞれダウンロードしましょう。上記のリンクから下記の画面に移動してください。それぞれ利用しているOSに合わせたものをダウンロードしてインストールしてください。
VirtualBoxとVagrantのVersionによってはうまく動かないことがあるようですが、私の場合はどちらも最新で問題ありませんでした。 2018/8/15現在でVirtualBox5.2.16,Vagrant2.1.2でした。もしうまくいかない場合はVirtualBoxのVersionを落とすと良いようです。
Vagrantの初期化
両方のインストールが終了したらコマンドプロンプトで作業を行います。まずはVagrantが正しくインストールされていることを確認しましょう。
vagrant -v Vagrant 2.1.2
続いて、仮想マシンを作成するためのディレクトリを作って移動します。
私の場合はVagrant/machineというディレクトリを作成しました。以下、一例です。machineではなく、目的に応じた分かりやすい名称にすれば良いと思います。
mkdir Vagrant cd Vagrant mkdir machine cd machine
移動したら、作業用ディレクトリの初期化を行います。初期化用のコマンドを実行すると下記のように表示され、「Vagrantfile」というVagrantの設定用ファイルが作成されます。
vagrant init A `Vagrantfile` has been placed in this directory. You are now ready to `vagrant up` your first virtual environment! Please read the comments in the Vagrantfile as well as documentation on `vagrantup.com` for more information on using Vagrant.
Ubuntu16.04の追加
Vagrantではboxと呼ばれるOSのイメージをまとめたファイルを追加することで仮想環境の構築を行います。boxはVagrantCloudというwebサイトで公開されているものを確認することができます。
私はこの中からubuntu/xenial64のboxを追加し利用しました。
boxを追加するには先ほど作成したVagrantfileを編集を行います。
15行目を以下のように変更します。
config.vm.box = "ubuntu/xenial64"
ファイルを編集したら以下のコマンドでboxを追加することができます。
vagrant box add ubuntu/xenial64
導入したboxの確認は以下のコマンドで行えます。
vagrant box list ubuntu/xenial64 (virtualbox, 20180814.0.0)
続いてネットワークの設定を行います。hostマシン(今回はwindows10)から仮想マシンにアクセスする際のIPアドレスなどが決定されます。Vagrantfileの35行目のコメントを解除します。
config.vm.network "private_network", ip: "192.168.33.10"
IPアドレスは変更することもできるようです。
最後にGUI環境構築のための設定を行います。Vagrantfileの52行目から58行目までのコメントを解除して以下のように設定し直しました。
config.vm.provider "virtualbox" do |vb| vb.gui = true vb.memory = "4096" vb.cpus = 2 vb.customize [ "modifyvm", :id, "--vram", "256", "--clipboard", "bidirectional", "--accelerate3d", "on", "--hwvirtex", "on", "--nestedpaging", "on", "--largepages", "on", "--ioapic", "on", "--pae", "on", "--paravirtprovider", "kvm", ] end
memoryやcpusの設定はお使いのコンピュータのスペックに合わせて変更しても良いでしょう。
Ubuntu16.04の起動
仮想マシンを起動します。
vagrant up
VirtualBoxで仮想マシンのウィンドウが開いているはずなので、ログインしましょう。
ユーザー名、パスワードともにvagrantとなっています。
vagrantのssh接続機能でも操作可能です。その場合は以下のコマンドを実行しましょう。
vagrant ssh
Ubuntu側で以下のコマンドを実行して、デスクトップ環境を構築します。
$ wget -q https://www.ubuntulinux.jp/ubuntu-ja-archive-keyring.gpg -O- | sudo apt-key add - $ wget -q https://www.ubuntulinux.jp/ubuntu-jp-ppa-keyring.gpg -O- | sudo apt-key add - $ sudo wget https://www.ubuntulinux.jp/sources.list.d/xenial.list -O /etc/apt/sources.list.d/ubuntu-ja.list $ sudo apt-get update $ sudo apt-get upgrade $ sudo apt-get install ubuntu-desktop
パッケージの導入が終了したら、仮想マシンを再起動します。コマンドプロンプト側で以下のコマンドを実行します。
vagrant reload
再起動後Ubuntuのデスクトップ画面が表示されていれば成功です。
仮想マシンを停止するときはUbuntu側からシャットダウンを行うか、コマンドプロントで以下のコマンドを実行します。
vagrant halt
VagrantでUbuntu16.04の導入は以上で終了です。近いうちにRails5の開発環境導入手順もまとめたいと思います。