20200217のMacに関する記事は10件です。

卒論執筆で便利なツール【卒論】【修論】【LaTeX】【Python】

この記事について

血反吐を吐きながら、2月中旬に卒論を書き終えました。

同期の友達(LaTeXを使い慣れていない人)が口を揃えて言うのが、「ググる時間が半端ない」です。
文法やLatexの仕様に関する知識は多いので、皆さんも苦戦した時期があったと思います。

LaTeXで卒論を書く際に、自分が使っていた面倒な作業を減らすためのツールを共通したいと思います。
無駄な作業は省いて、じっくり文章を推敲しましょう。

以下の人向けの記事になっています。

  • 卒論・修論執筆でLaTeXを使用する人
  • 研究でPythonを利用する人

卒論執筆で便利なツール

1. VSCode

LaTeX を実際に書いていくエディタにはVSCodeを採用しました。LaTeXの拡張機能が充実していることが理由の1つです。

インストールは以下のQiita記事を参考にしてください。
[Mac] MacOSでVisual Studio Codeをインストールする手順
[Windows] VScodeのインストール手順@Windows10

※ LaTeXをインストールせずとも、Webブラウザ上でLaTeXを使用できるサービスはいくつか存在します(Cloud LaTeX, Overleaf, Share LaTeXなど)。しかし、通信の関係上画面が固まってしまう問題や、資料をweb上にアップロードする必要など不便な面が目立ちます。筆者は1度だけCloud LaTeXを使用したことがありますが、画像を多くアップロードすると、画面のカクツキが酷くなるなどが見られたためローカルでの執筆に切り替えました。

LaTeX Workshop (VSCodeの拡張機能)

  • 入力補完、候補表示
  • 保存時にコンパイルを実行
  • 分割画面でpdfプレビュー
  • Snippets
  • マウスオーバー機能

これらの作業を補助してくれるので、作業時間が大幅に減ります。

インストールは以下の記事を参考にしてください。
LaTeX Workshopってなに?どうやって使うの?調べてみた!

2. Mathpix Snipping Tool

Web上や論文内に含まれる数式を、スクリーンショットで撮影するだけで自動的にLaTeXの数式記法に変換してくれる機能です。数式を引用する場合に、論文と照らし合わせながら数式を記述するのは、大きな時間ロスになってしまいます。

ググる内容も数式関係は多くなりがち...

機械学習分野などは理論の説明も多くなることがありますので、このツールは便利だと思います。

使い方も非常にシンプルです。
アプリケーションを起動し、スクリーンショットを部分的に撮影します。
???

その後、正しく変換されているか確認します。もし、修正点があればその場で書き直すこともできます。後は、好みの記法をコピーして貼り付けるだけで終了です。
???

LaTeX きっかけで数式嫌いになるのは馬鹿らしいですもんね。

3. LaTeX Tables Generator

Web上で表を簡単に作れるサイトです。Excel、PowerPointやKeynoteで表を作成する要領で、直感的に表を作成することができます。

表を作成したら、Generateボタンを押してコードをコピペするだけです。
???

詳しい説明はこちらが参考になると思います。

LaTeX Complex Table Editorというサイトもあります。

4. Pythonで画像生成する際の注意

Jupyter NotebookやJupyter Lab、Google Colaboratoryなどでプログラムを動かしている人が多いと思います。そこで、論文に載せる資料(画像)をPythonで描画する人に意識してほしいことがあります。

グラフのフォントサイズ

Jupyter Labなどでグラフを描画する際は、パソコンの大きな画面で見ているためにグラフのラベル文字・メモリが小さいと思うことは少ないです。
しかし、画像を論文に貼り付けた時に文字が小さくて見にくい!印刷した時に潰れてしまった!ということがよく起きます。

図を描画する際に、フォントサイズを大きくしましょう。

Before After
sc0.png sc1.png

この記事より引用させていただきました。詳しい方法が紹介されています。

例えば、各グラフに対してフォントサイズを設定する場合↓

フォントサイズ設定
~
plt.title("Title", fontsize=18)
plt.xlabel("xlabel", fontsize=18)
plt.ylabel("ylabel", fontsize=18)
plt.legend(fontsize=18)
plt.tick_params(labelsize=18)
~

まとめてフォントサイズを設定する場合↓

まとめてフォントサイズ設定
plt.rcParams["font.size"] = 18

画像は余白詰めて保存する

LaTeXで画像を添付するとき、その画像の下部分に余白があるとCaptionと画像間に大きな空白が生じてしまいます。これを回避するためにあらかじめ余白がない画像を保存するようにしましょう。

余白を詰めて保存
# pad_inchesで余白の大きさ指定
plt.savefig("Chart.jpg", bbox_inches='tight', pad_inches=0)

画像はEPS形式かPDF形式で保存する

LaTeXに画像を添付する際は、eps拡張子pdf拡張子の画像が推奨されています。理由として、jpg形式などの画像を添付すると、印刷やプレビューの際にぼやけた表示になってしまうことがあります。(png形式は比較的綺麗です。)

画像を保存する際は拡張子を指定するだけでOKです。

eps,pdf拡張子で保存
# eps形式で保存
plt.savefig('image.eps')
# pdf形式で保存
plt.savefig('image.pdf')

jpgなどの画像をpdf形式に変換するコード(png画像は不可)
インターネットから取得した画像などを使用する際にはこの作業が必要になると思います。

pdf形式に変換
from matplotlib.backends.backend_pdf import PdPages
import img2pdf

# pdfファイルへ変換
cov_pdf = img2pdf.convert("image.jpg")

# pdfファイルを読み込み(pdf_nameで指定したpdfがない場合、pdf_nameをファイル名として新規にpdfファイルを作成)
file = open(pdf_name , "wb")

# pdfファイルを書き込み
file.write(cov_pdf)

# 開いているファイルを閉じる
file.close()

png画像をpdf形式に変換
png画像はアルファチャネルが存在するため、一度jpg形式に変換する必要があります。

png→pdf
from matplotlib.backends.backend_pdf import PdPages
import img2pdf
from PIL import Image


# 画像読み込み
png_img = Image.open("image.png")

# RGBA→RGBに変換
rgb_img = png_img.convert('RGB')

# jpgに保存し直す
rgb_img.save("image.jpg", quality=99)

# pdfファイルへ変換
cov_pdf = img2pdf.convert("image.jpg")

# pdfファイルを読み込み(pdf_nameで指定したpdfがない場合、pdf_nameをファイル名として新規にpdfファイルを作成)
file = open(pdf_name , "wb")

# pdfファイルを書き込み
file.write(cov_pdf)

# 開いているファイルを閉じる
file.close()

画像を作り直したり、画像編集ツールを使用したり、煩わしい作業は最初から対策しておきましょう。

5. 参考文献は可能な限り、手作業で書かない

BiBTeX

BiBTeXはLaTeXにおける参考文献データベースと説明されます。
つまり、自分自身で参考文献リストを作成し、その名前を指定するだけで自動的に参考文献欄への記述を行ってくれる仕組みです。参考文献の書き方にはルールが存在するため、自力で記述するとなると時間がかかってしまいます。

参考文献データベースを作成するには、filename.bibに文献情報をコピペしていきます。

BiBTeX入門
BiBTeXとは

Google Schlar

Googleが運営する文献検索サイトです。
検索する際に欲しい論文のbibtexも取得することができます。

  1. 論文を検索し、引用マークをクリックします。
    google.png

  2. BibTexを選択し、filename.bibにコピペしたら終了です。
    google2.png

arXivなどの他の論文検索サイトでも必ずbibtexを取得できますので、論文読みの際に同時に保存しておきましょう。
書籍の参考文献なら Lead2Amazon がオススメです。

個人的に地味に便利と思ったLaTeXの文法

行文章番号

教授や先輩に添削をお願いする場合などに行番号があると、添削コメントをスムーズに書きやすくなると思います。

行番号設定
% パッケージ
\usepackege{lineno}
~
\section{文章番号}
% 行番号のカウントを始めたい部分に記述
\linenumbers
卒業論文修士論文博士論文...

16236018.png

強制的に図を配置する

図を自分が指定した場所に載せたいときに使用します。通常の指定では自動で場所が変更されることがありますが、それを無効化します。参考記事はこちら

強制的に図を配置
% パッケージに追加
\usepackage{here}
~
% "H"で指定
\begin{figure}[H]

図のサイズをページ幅で指定

ページ幅
% ページ幅の50%を指定
\begin{minipage}{0.50\hsize}        

\begin{equation}の前は空白を入れない

数式を書く際に\begin{equation}の前に空白を入れると、数式の前後のスペースのバランスがおかしくなります。

空白を入れる
0123456789...
%%%%%空白部分%%%%%
\begin{equation}
   x^2 - 6x + 1 = 0 
\end{equation}

...0123456789

スクリーンショット 2020-02-17 23.03.40.png
数式の前後のスペースの大きさがアンバランスです。

空白を消してみましょう。

空白を入れない
0123456789...
\begin{equation}
   x^2 - 6x + 1 = 0 
\end{equation}
...0123456789

スクリーンショット 2020-02-17 23.04.06.png

数式の前後に綺麗にスペースが入っています。

終わりに

最後までお読みいただきありがとうございます。
今回の卒論執筆でお世話になったツールや文法を紹介させていただきました。
お役に立てれば光栄です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

30分でPython入門!開発環境構築&基礎文法を学ぶ【有益な記事総まとめ】

Pythonを始めたきっかけ

TypescriptとGASでChatBotを作ったり
競技プログラミング(AtCoder)にC#で挑戦してみたりする中で、
これPython使えたらめっちゃ楽なのでは??
と感じる場面が非常に多かったので、勢いで軽率に入門をキメました(σ・ω・)σ

やりたいこと

本記事では、macOSVSCodeを使って
Pythonをゴリゴリ記述していくための開発環境を構築
していきます。

ただ、Python入門に必要となる以下の作業などについては、
世の中の強い方々が既に言語化して下さっています。

✔︎ Pythonのインストール方法
✔︎ 自動整形/コーディング規約の設定
✔︎ PyPyの基礎知識
✔︎ Python3の基礎文法
✔︎ 競プロ用チートシート

ですので、本記事では「2020年2月現在、この順番でこの記事を読んで適切にセッティングしていけば確実にPython入門決められるよ!」という点に価値を見出し、Python入門者にとってのハブとして機能するよう、必要最小限の内容を簡単にまとめました。(・ω・。)

Python入門に有益なドキュメント

以下にジャンル別で参考になった記事を紹介していきます。

まずは公式ドキュメント

公式ドキュメントは形式張っていて読みにくいことが多いですが、日本語版があるだけマシだと思って津留を通しましょう。下記の用語集標準ライブラリなどについては仕様が分かりやすくオススメです。

【公式】Python チュートリアル
【公式】Python 用語集
【公式】Python 標準ライブラリ

Pythonのインストール

インストールにpyenvを使う場合と使わない場合と、数種類の記事が散見されますが、Pythonはバージョン間の互換性が薄い場合があることなどを考慮すると、pyenvを使った方がバージョン管理の都合が良さそうです。
pyenvを使ってMacにPythonの環境を構築する

自動整形、コーディング規約の調整

記法に制約が比較的多いPythonの場合、自動整形の設定をしないのは死活問題になりそうです。特に入門者の場合は無用な記法エラーで時間を取られそうなので、何よりもまず導入すべきだと思います。
VSCodeのPython開発環境でpylintの代わりにflake8を導入し自動整形を設定する

上記記事の補足として下記記事の内容も参照すると良い感じになります。
VS Code コーディング規約を快適に守る

Pythonの基礎文法

他の言語をやったことある方なら、下記の記事を一通りこなすだけでそこそこPythonを扱いこなせるようになると思います。
Python3基礎文法

PyPyとは何か

PyPyを用いると、場面にもよりますが普通のPythonよりも処理が格段に早くなることが多いようです。
PyPyの基礎知識まとめ その1

競プロ関連のTips

基礎文法を学び終わったら、早速AtCoderでゴリゴリにアルゴリズムを実装してPython経験値を高めていきましょう。下記の記事3つを参考にすれば、入門者でもABCのD問題くらいまでは手出せるようになるかと!

Pythonで使う競技プログラミング用チートシート
Pythonで競プロやるときによく書くコードをまとめてみた
PythonでAtCoder青になるまで -Pythonで競プロやるときに気をつけること-

まとめ

とりあえずここら辺までPythonについての情報収集、開発環境の構築ができたなら、ここから先Pythonユーザとして実践に踏み出ていくことは容易だと思います。
末筆にはなりますが、Python熟練者の皆さま、入門者にはぜひこの記事を!といった参考情報ございましたら、お気軽にコメントお願いします!| ε:)

ついったフォローしてね。(・ω・。)
@NadjaHarold

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

30分でPython入門!開発環境構築&基礎文法を学ぶ【2020年2月現在有益な記事総まとめ】

Pythonを始めたきっかけ

TypescriptとGASでChatBotを作ったり
競技プログラミング(AtCoder)にC#で挑戦してみたりする中で、
これPython使えたらめっちゃ楽なのでは??
と感じる場面が非常に多かったので、勢いで軽率に入門をキメました(σ・ω・)σ

やりたいこと

本記事では、macOSVSCodeを使って
Pythonをゴリゴリ記述していくための開発環境を構築
していきます。

ただ、Python入門に必要となる以下の作業などについては、
世の中の強い方々が既に言語化して下さっています。

✔︎ Pythonのインストール方法
✔︎ 自動整形/コーディング規約の設定
✔︎ PyPyの基礎知識
✔︎ Python3の基礎文法
✔︎ 競プロ用チートシート

ですので、本記事では「2020年2月現在、この順番でこの記事を読んで適切にセッティングしていけば確実にPython入門決められるよ!」という点に価値を見出し、Python入門者にとってのハブとして機能するよう、必要最小限の内容を簡単にまとめました。(・ω・。)

Python入門に有益なドキュメント

以下にジャンル別で参考になった記事を紹介していきます。

まずは公式ドキュメント

公式ドキュメントは形式張っていて読みにくいことが多いですが、日本語版があるだけマシだと思って都度目を通しましょう。下記の用語集標準ライブラリなどについては仕様が分かりやすくオススメです。

【公式】Python チュートリアル
【公式】Python 用語集
【公式】Python 標準ライブラリ

Pythonのインストール

インストールにpyenvを使う場合と使わない場合と、数種類の記事が散見されますが、Pythonはバージョン間の互換性が薄い場合があることなどを考慮すると、pyenvを使った方がバージョン管理の都合が良さそうです。
pyenvを使ってMacにPythonの環境を構築する

自動整形、コーディング規約の調整

記法に制約が比較的多いPythonの場合、自動整形の設定をしないのは死活問題になりそうです。特に入門者の場合は無用な記法エラーで時間を取られそうなので、何よりもまず導入すべきだと思います。
VSCodeのPython開発環境でpylintの代わりにflake8を導入し自動整形を設定する

上記記事の補足として下記記事の内容も参照すると良い感じになります。
VS Code コーディング規約を快適に守る

Pythonの基礎文法

他の言語をやったことある方なら、下記の記事を一通りこなすだけでそこそこPythonを扱いこなせるようになると思います。
Python3基礎文法

PyPyとは何か

PyPyを用いると、場面にもよりますが普通のPythonよりも処理が格段に早くなることが多いようです。
PyPyの基礎知識まとめ その1

競プロ関連のTips

基礎文法を学び終わったら、早速AtCoderでゴリゴリにアルゴリズムを実装してPython経験値を高めていきましょう。下記の記事3つを参考にすれば、入門者でもABCのD問題くらいまでは手出せるようになるかと!

Pythonで使う競技プログラミング用チートシート
Pythonで競プロやるときによく書くコードをまとめてみた
PythonでAtCoder青になるまで -Pythonで競プロやるときに気をつけること-

まとめ

とりあえずここら辺までPythonについての情報収集、開発環境の構築ができたなら、ここから先Pythonユーザとして実践に踏み出ていくことは容易だと思います。
末筆にはなりますが、Python熟練者の皆さま、入門者にはぜひこの記事を!といった参考情報ございましたら、お気軽にコメントお願いします!| ε:)

ついったフォローしてね。(・ω・。)
@NadjaHarold

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)の解決方法

rbenvで複数のrubyバージョンが存在する時に、bundle installを行うと、以下のエラーが発生。

can't find gem bundler (>= 0.a) with executable bundle (Gem::GemNotFoundException)

該当するrubyのバージョンを再インストールするも状況は改善されず。

いろいろと探した結果、bundlerのバージョンとGemfile.lock側のbundlerのバージョンが異なることで発生していたようなので、

$ gem install bundler -v 該当バージョン

これで、バージョンの相違を解消してあげればOK。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Macでスクリーンショットを撮る

Macでスクリーンショットを撮る
shift + command + 3

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

macbookair13インチ2015を広い画面で使ってみたかった。WUXGA風(1920x1200)とWSXGA+風(1680x1050)

macbookair13インチを広い画面で使いたい

そろそろ買い替えを考えている私のmacbookair(2015)
コードを書くわけでもなく、ただのメモ&検索用端末と化しているけれども
たまに感じる唯一の不満
「画面が狭い...」
この不満をgoogle様にぶつけてみたら
なにやらタダで疑似解像度を追加して無理やり広くする方法があるとお告げを受けた

やってみる

参考にさせていただいた素晴らしいサイト様の情報に基づいて

Macintosh HD -> システム -> ライブラリ-> Displays -> Contents -> Resources -> Overrides -> DisplayVendorID-610 -> DisplayProductID-9cf0

を編集
arrayタグの中に以下をおまじない的に追加してみる

DisplayProductID-9cf0
<data>AAANIAAACDQAAAAB</data>
<data>AAAPAAAACWAAAAAB</data>

ちょっとワクワクしながら再起動
3360x2100
3840x2400
...:innocent:
凄まじい解像度が増えている??:thinking:

どうしてこうなった?

あの追加した文字列は16進数をbase64エンコードしたもので
その16進数がまんまディスプレイの解像度になっているとか、いないとか...

真実を確認するために変換してみる

おまじない1の文字列
AAANIAAACDQAAAAB
↓16進変換
00000D200000083400000001
↓8桁ごとに分解
00000D20 00000834 00000001
↓1つ目と2つ目を10進にすると
3360x2100
おまじない2の文字列
AAAPAAAACWAAAAAB
↓16進変換
00000D20 00000834 00000001
↓
3840x2400

追加された解像度と一致した。

追加すべき文字列を作ってみる

欲しい解像度1:1680x1050
1680は16進数で690、1050は16進数で41a
↓最後の1はフラグっぽいのでそのまま付けてみる
00000690 0000041a 00000001
↓Base64変換
AAAGkAAABBoAAAAB
欲しい解像度2:1920x1200
1920は16進で7bc、1200は4b0
↓
000007bc 000004b0 00000001
↓Base64変換
AAAHgAAABLAAAAAB

arrayタグの中に追加すべきだった本当の文字列

DisplayProductID-9cf0
<data>AAAGkAAABBoAAAAB</data>
<data>AAAHgAAABLAAAAAB</data>

追加、再起動後
無事、1920x1200と1660x1050が追加された
解像度追加したい欲求が満たされた!

参考にさせていただいた素晴らしいサイト様

https://ji0vwl.net/index.php/2017/01/15/766/
https://ji0vwl.net/index.php/2019/10/19/2582/
https://ameblo.jp/jochi-masaharu/entry-12041077596.html

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

邪悪なDocker for Macを捨てて最速のDocker環境を手に入れる

Macでネイティブでの動作とほぼ同等の速度のDockerを手に入れることができたので、その知見について公開します。
ものによりますが、最大10倍程度パフォーマンスの向上が見られました。
※タイトルには若干誇張が含まれています

Docker for Macは遅い

MacでDockerを使って開発している方は体感していると思うのですが、Docker for Macの速度はネイティブで動かすのと比べて相当遅く、リソースも消費します。
自分はRails + React(webpack) というWebでは比較的一般的なスタックで開発しているのですが、Railsの初期読み込み・webpackの(差分)トランスパイル・yarn installなどが致命的に重く、LinuxでのDocker環境と比較してかなりDXが低下します。
特に大量のファイルの読み込みや、ファイル変更のリアルタイム検知が弱い印象が強いです。

また、Docker for Macは不安定な面もあり、(例えばgitのブランチ変更などで)大量のファイル変更が起こった場合にCPU使用率が100%に張り付いてしまい、再起動しないと戻ってこないなどの現象も起きることがあります。
com.docker.hyperkit 100% cpu usage is back again · Issue #3499 · docker/for-mac

この問題の大部分は調べた限りファイルシステムをマウントした際のオーバーヘッドに起因しており、docker-syncなどを使ってファイルシステムの扱い方を変えることである程度改善することなどが知られています。
しかし、docker-syncも不安定さが残るなど、懸念点があります。
参考: hanhan's blog - Docker for Macのmount遅い問題まとめ

また、自分は設定の煩雑さから使ったことがないのですが、Docker for MacはNFS Volume sharingに対応しているので、それを使うことでもある程度改善されるのではないかと思います。

この記事ではパフォーマンス低下の最も大きな原因となっているファイルシステムについていくつか改善案を試し、その中で最も効果があったVirtualBox(Ubuntu)を使い、ファイルシステムのマウントを行わずに(VMのネイティブ?ファイルシステム上で)Dockerを使う、という手法について解説しようと思います。

Docker for Macと今回構築する環境(VirtualBox + Docker)の違い

docker.png

基本的にはどちらもVM上でDockerが動作することになります。

Docker for Mac

Docker for Macでは、インストール時にAlphine LinuxベースのHyperkit VMがインストールされ、起動時に /var/run/docker.sock が生成されます。
このVMはDocker for Mac上で自動的に管理され、Mac上で docker, docker-compose コマンドを実行する際に特に設定をしていない限り docker.sock を経由して透過的にVM内でDockerコマンドが実行されるようになっています。

VirtualBox + Docker

一方、今回構築する環境ではVirtualBoxを使って実際にVMを管理することになります。
Vagrantを用いてVirtualBoxでUbuntu環境を構築し、その中でDockerを動作させます。
完全に独立した環境のため、ファイルシステムについては VMに設定しているものがDocker上にマウントされ 、Dockerコマンドについてはsshで接続した上でVM内から実行することになります。
VM内の docker.sock をMacにマウントすることでMac上から透過的にDockerコマンドを扱えるかもしれませんが、まだ検証していません。

ファイルシステムについてですが、Vagrantでファイルを同期する sync_folder 機能で指定できる同期タイプとして

  • VirtualBox(オプション指定なし)
  • NFS
  • rsync
  • SMB

があります。
しかし、今回はどれも使わずに(VM起動時に1度だけrsyncをする)、Vagrantから独立した手段でファイル同期を行います。

実際の速度差

Docker for Mac, VirtualBox + Docker, Macネイティブについて、計測が容易で差が大きかったものについて環境毎の実測値を記載します。

Command Docker for Mac VirtualBox + Docker Macネイティブ
yarn install 117.84s 117.84s 16.88s 6.8倍高速化
Rails起動後の初回アクセス
(curl)
22.43s 2.199s 3.32s 10倍高速化

VMの設定や使い方

VMの構築・設定

Vagrantを使ってVMを構築します。
実際に使っているVagrantfileの構成は以下です。
CPU・メモリ・ディスクサイズなどは適宜調整してください。

追加でプラグインとして

  • vagrant-disksize
  • vagrant-hostsupdater
  • vagrant-mutagen

が必要になるので、 $ vagrant plugin install vagrant-disksize vagrant-hostsupdater vagrant-mutagen を実行してプラグインをインストールしておいてください。

Vagrant.configure('2') do |config|
  config.vm.box = 'ubuntu/xenial64'

  config.vm.hostname = 'my-app'

  config.vm.network :private_network, ip: '192.168.50.10'

  config.vm.provider :virtualbox do |vb|
    vb.gui = false
    vb.cpus = 4
    vb.memory = 8192
    vb.customize ['modifyvm', :id, '--natdnsproxy1', 'off']
    vb.customize ['modifyvm', :id, '--natdnshostresolver1', 'off']
  end

  config.disksize.size = '30GB'
  config.mutagen.orchestrate = true

  config.vm.synced_folder './', '/home/vagrant/app', type: "rsync",
    rsync_auto: true,
    rsync__exclude: ['.git/', 'node_modules/', 'log/', 'tmp/']

  config.vm.provision 'shell', inline: <<-SHELL
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh
    usermod -aG docker vagrant

    curl -L "https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    chmod +x /usr/local/bin/docker-compose
  SHELL
end

VMの使い方

(実際のコマンドの実行はファイル同期の章に書いている内容を行った後にしてください。)
$ vagrant up を実行すると各設定が適用されたVMの作成・起動とrsyncの実行が行われ、 .//home/vagrant/app に同期されます。rsyncの実行は起動時のみで自動的な実行は行われません。
初回はprovisionでDockerとDocker Composeのインストールが行われます。

その後、 $ vagrant ssh をしてVMにSSHでログインし、 $ cd app && docker-compose up {rails} のようなコマンドを実行することでDockerコンテナが立ち上がります。
イメージのビルドなどの処理は必要に応じて適宜行ってください。
立ち上がったアプリケーションにはVagrantfileで指定しているIPでアクセスできます。 (http://192.168.50.10:{3000} など)

ファイル同期の手段

開発を行う際にはローカルとVM上でファイルを同期する必要がありますが、その際にVirtualBoxの標準の共有フォルダやNFSを使うと、VM内からDockerへマウントした際のパフォーマンスが結局大幅に低下します。
そのため、MacからVMへのファイルシステム自体のマウントは行わず、ファイル変更を検知して相互にファイルを転送するような同期手段が望ましいです。

Vagrantのrsyncでの同期がそれに近いのですが、その場合ホストからVMへ単方向の同期しか行われないという問題があります。
具体的にはVM内で $ bundle install$ yarn install を実行した際に更新される Gemfile.lockyarn.lock のようなファイルについてVMからMacへ同期が行われません。

VM内のファイル変更を検知してホストにrsyncを実行するようなコマンド実行も試したのですが、双方向にrsyncで適切な同期を行うというのは実現できませんでした。

ファイル同期のソリューション 「Mutagen

上記の問題についてですが、最終的にMutagenを使うことで解決しました。
VMの構築・設定の節で既に記載しているのですが、Mutagenをインストールし、 vagrant-mutagen プラグインを導入することで双方向のファイル同期が適切に行われます。
Mutagenのインストールですが、Macでは $ brew install mutagen-io/mutagen/mutagen を実行することで行う事ができます。

その後、Vagrantfileと同じ階層に以下の mutagen.yml を配置してください。

sync:
  app:
    mode: "two-way-resolved"
    alpha: "./"
    beta: "{my-app (Vagrantfileで指定したホスト名)}:/home/vagrant/app"
    ignore:
      vcs: true
      paths:
        - "/node_modules"
        - "/log"
        - "/tmp"

その後 $ vagrant up でVMを起動すると、Mutagenによって双方向のファイル同期が行われます。
この同期は双方向にほぼリアルタイムで行われ、ファイルシステムのマウントではなくファイルの転送で実現されているため、最終的にDockerコンテナへマウントされた際のオーバーヘッドはほぼ発生しません。

結果

結果として現在業務で行っている開発にVagrantとMutagenを導入したことで、Macでも快適にDockerを使った開発が行えるようになりました。
Docker for Macでは速度・リソース・安定性のどの面でも苦労していたのですが、現在はある程度安定したDocker環境を構築できています。
sshしなければ使えないなど多少不便な点も残ってはいるのですが、それを上回るメリットが享受できていると感じています。

Docker for Macでの開発に消耗している方は数多くいる印象なので、もし機会があればこの構成を試してみていただけると幸いです。

備考・注意点

実行環境のスペック

MacBook Pro (16-inch, 2019)
CPU: 2.4GHz 8コア Intel Core i9
メモリ: 64GB 2667MHz DDR4

Docker for MacへのMutagenの適用

MutagenはDocker for Macで扱うこともできるようです。
その際のパフォーマンスは計測できていないのですが、もしかしたらVagrantを経由した場合と同等の速度が出るかもしれません。

今回はMutagen導入時には既にVagrant + rsyncを使った場合の速度が非常に速いことが確認できていたので、設定がシンプルだったvagrant-mutagenを採用したのですが、Docker for Mac + Mutagenでの開発も調査する価値があるかと思います。

マイクロサービス開発への対応

場合によっては複数のリポジトリに横断するマイクロサービス開発をDockerのnetwork機能を用いて行っている場合があるかと思います。
この構成ではリポジトリ毎に完全に独立したVMが作成されるという特性上、Docker networkを使った開発が難しくなってしまう可能性があります。

現在マイクロサービスでの開発は行っていないため確認できていないのですが、解決策として

  • 上記Docker for MacへのMutagenの適用
  • 必要なマイクロサービスをsubtree(submodule)として配置したリポジトリを作り、そのリポジトリ上でVMを構築する

というものが使えないかと考えています。
これについては今後マイクロサービス開発が必要になったタイミングで検証を進めていこうかと考えています。

Mutagenの同期セッションについて

vagrant-mutagenでのファイル同期についてですが、 $ vagrant halt を行わずにVMが終了した場合にはセッションが削除されずに残ってしまうという問題が確認できています。
セッションが残っていた場合には次回のVM起動時にセッションが新たに作成され、多重にセッションが貼られたまま残り続けるという状態になってしまいます。
また、残っているセッションは mutagen.yml の設定を読み込まずに古い設定のまま動作し続けるためバグの原因となります(なりました)。

MacのクラッシュやVMの正常な終了が行えなかった際には $ mutagen sync list でセッションを確認し、不要なセッションについては $ mutagen sync terminate {session_id} で削除するようにしてください。

docker-composeの設定について

docker-compose.yml に以下のようにgem用、npm用のvolumeを作成しているのですが、設定に不備があってDocker for Macでパフォーマンスが落ちているなどあればコメントをいただけると助かります。

services:
  rails: &app_base
    volumes:
      - .:/usr/src/app:cached
      - bundle:/usr/local/bundle:cached
  frontend:
    volumes:
      - .:/usr/src/app:cached
      - npm:/usr/local/npm:cached

volumes:
  bundle:
  npm:
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

邪悪なDocker for Macを捨ててMac最速のDocker環境を手に入れる

Macでネイティブでの動作とほぼ同等の速度のDockerを手に入れることができたので、その知見について公開します。
ものによりますが、最大10倍程度パフォーマンスの向上が見られました。
※タイトルには若干誇張が含まれています

Docker for Macは遅い

MacでDockerを使って開発している方は体感していると思うのですが、Docker for Macの速度はネイティブで動かすのと比べて相当遅く、リソースも消費します。
自分はRails + React(webpack) というWebでは比較的一般的なスタックで開発しているのですが、Railsの初期読み込み・webpackの(差分)トランスパイル・yarn installなどが致命的に重く、LinuxでのDocker環境と比較してかなりDXが低下します。
特に大量のファイルの読み込みや、ファイル変更のリアルタイム検知が弱い印象が強いです。

また、Docker for Macは不安定な面もあり、(例えばgitのブランチ変更などで)大量のファイル変更が起こった場合にCPU使用率が100%に張り付いてしまい、再起動しないと戻ってこないなどの現象も起きることがあります。
com.docker.hyperkit 100% cpu usage is back again · Issue #3499 · docker/for-mac

この問題の大部分は調べた限りファイルシステムをマウントした際のオーバーヘッドに起因しており、docker-syncなどを使ってファイルシステムの扱い方を変えることである程度改善することなどが知られています。
しかし、docker-syncも不安定さが残るなど、懸念点があります。
参考: hanhan's blog - Docker for Macのmount遅い問題まとめ

また、自分は設定の煩雑さから使ったことがないのですが、Docker for MacはNFS Volume sharingに対応しているので、それを使うことでもある程度改善されるのではないかと思います。

この記事ではパフォーマンス低下の最も大きな原因となっているファイルシステムについていくつか改善案を試し、その中で最も効果があったVirtualBox(Ubuntu)を使い、ファイルシステムのマウントを行わずに(VMのネイティブ?ファイルシステム上で)Dockerを使う、という手法について解説しようと思います。

Docker for Macと今回構築する環境(VirtualBox + Docker)の違い

docker.png

基本的にはどちらもVM上でDockerが動作することになります。

Docker for Mac

Docker for Macでは、インストール時にAlphine LinuxベースのHyperkit VMがインストールされ、起動時に /var/run/docker.sock が生成されます。
このVMはDocker for Mac上で自動的に管理され、Mac上で docker, docker-compose コマンドを実行する際に特に設定をしていない限り docker.sock を経由して透過的にVM内でDockerコマンドが実行されるようになっています。

VirtualBox + Docker

一方、今回構築する環境ではVirtualBoxを使って実際にVMを管理することになります。
Vagrantを用いてVirtualBoxでUbuntu環境を構築し、その中でDockerを動作させます。
完全に独立した環境のため、ファイルシステムについては VMに設定しているものがDocker上にマウントされ 、Dockerコマンドについてはsshで接続した上でVM内から実行することになります。
VM内の docker.sock をMacにマウントすることでMac上から透過的にDockerコマンドを扱えるかもしれませんが、まだ検証していません。

ファイルシステムについてですが、Vagrantでファイルを同期する sync_folder 機能で指定できる同期タイプとして

  • VirtualBox(オプション指定なし)
  • NFS
  • rsync
  • SMB

があります。
しかし、今回はどれも使わずに(VM起動時に1度だけrsyncをする)、Vagrantから独立した手段でファイル同期を行います。

実際の速度差

Docker for Mac, VirtualBox + Docker, Macネイティブについて、計測が容易で差が大きかったものについて環境毎の実測値を記載します。

Command Docker for Mac VirtualBox + Docker Macネイティブ
yarn install 117.84s 16.86s 16.88s 6.8倍高速化
Rails起動後の初回アクセス
(curl)
22.43s 2.199s 3.32s 10倍高速化

VMの設定や使い方

VMの構築・設定

Vagrantを使ってVMを構築します。
実際に使っているVagrantfileの構成は以下です。
CPU・メモリ・ディスクサイズなどは適宜調整してください。

追加でプラグインとして

  • vagrant-disksize
  • vagrant-hostsupdater
  • vagrant-mutagen

が必要になるので、 $ vagrant plugin install vagrant-disksize vagrant-hostsupdater vagrant-mutagen を実行してプラグインをインストールしておいてください。

Vagrant.configure('2') do |config|
  config.vm.box = 'ubuntu/xenial64'

  config.vm.hostname = 'my-app'

  config.vm.network :private_network, ip: '192.168.50.10'

  config.vm.provider :virtualbox do |vb|
    vb.gui = false
    vb.cpus = 4
    vb.memory = 8192
    vb.customize ['modifyvm', :id, '--natdnsproxy1', 'off']
    vb.customize ['modifyvm', :id, '--natdnshostresolver1', 'off']
  end

  config.disksize.size = '30GB'
  config.mutagen.orchestrate = true

  config.vm.synced_folder './', '/home/vagrant/app', type: "rsync",
    rsync_auto: true,
    rsync__exclude: ['.git/', 'node_modules/', 'log/', 'tmp/']

  config.vm.provision 'shell', inline: <<-SHELL
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh
    usermod -aG docker vagrant

    curl -L "https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    chmod +x /usr/local/bin/docker-compose
  SHELL
end

VMの使い方

(実際のコマンドの実行はファイル同期の章に書いている内容を行った後にしてください。)
$ vagrant up を実行すると各設定が適用されたVMの作成・起動とrsyncの実行が行われ、 .//home/vagrant/app に同期されます。rsyncの実行は起動時のみで自動的な実行は行われません。
初回はprovisionでDockerとDocker Composeのインストールが行われます。

その後、 $ vagrant ssh をしてVMにSSHでログインし、 $ cd app && docker-compose up {rails} のようなコマンドを実行することでDockerコンテナが立ち上がります。
イメージのビルドなどの処理は必要に応じて適宜行ってください。
立ち上がったアプリケーションにはVagrantfileで指定しているIPでアクセスできます。 (http://192.168.50.10:{3000} など)

ファイル同期の手段

開発を行う際にはローカルとVM上でファイルを同期する必要がありますが、その際にVirtualBoxの標準の共有フォルダやNFSを使うと、VM内からDockerへマウントした際のパフォーマンスが結局大幅に低下します。
そのため、MacからVMへのファイルシステム自体のマウントは行わず、ファイル変更を検知して相互にファイルを転送するような同期手段が望ましいです。

Vagrantのrsyncでの同期がそれに近いのですが、その場合ホストからVMへ単方向の同期しか行われないという問題があります。
具体的にはVM内で $ bundle install$ yarn install を実行した際に更新される Gemfile.lockyarn.lock のようなファイルについてVMからMacへ同期が行われません。

VM内のファイル変更を検知してホストにrsyncを実行するようなコマンド実行も試したのですが、双方向にrsyncで適切な同期を行うというのは実現できませんでした。

ファイル同期のソリューション 「Mutagen

上記の問題についてですが、最終的にMutagenを使うことで解決しました。
VMの構築・設定の節で既に記載しているのですが、Mutagenをインストールし、 vagrant-mutagen プラグインを導入することで双方向のファイル同期が適切に行われます。
Mutagenのインストールですが、Macでは $ brew install mutagen-io/mutagen/mutagen を実行することで行う事ができます。

その後、Vagrantfileと同じ階層に以下の mutagen.yml を配置してください。

sync:
  app:
    mode: "two-way-resolved"
    alpha: "./"
    beta: "{my-app (Vagrantfileで指定したホスト名)}:/home/vagrant/app"
    ignore:
      vcs: true
      paths:
        - "/node_modules"
        - "/log"
        - "/tmp"

その後 $ vagrant up でVMを起動すると、Mutagenによって双方向のファイル同期が行われます。
この同期は双方向にほぼリアルタイムで行われ、ファイルシステムのマウントではなくファイルの転送で実現されているため、最終的にDockerコンテナへマウントされた際のオーバーヘッドはほぼ発生しません。

結果

結果として現在業務で行っている開発にVagrantとMutagenを導入したことで、Macでも快適にDockerを使った開発が行えるようになりました。
Docker for Macでは速度・リソース・安定性のどの面でも苦労していたのですが、現在はある程度安定したDocker環境を構築できています。
sshしなければ使えないなど多少不便な点も残ってはいるのですが、それを上回るメリットが享受できていると感じています。

Docker for Macでの開発に消耗している方は数多くいる印象なので、もし機会があればこの構成を試してみていただけると幸いです。

備考・注意点

実行環境のスペック

MacBook Pro (16-inch, 2019)
CPU: 2.4GHz 8コア Intel Core i9
メモリ: 64GB 2667MHz DDR4

Docker for MacへのMutagenの適用

MutagenはDocker for Macで扱うこともできるようです。
その際のパフォーマンスは計測できていないのですが、もしかしたらVagrantを経由した場合と同等の速度が出るかもしれません。

今回はMutagen導入時には既にVagrant + rsyncを使った場合の速度が非常に速いことが確認できていたので、設定がシンプルだったvagrant-mutagenを採用したのですが、Docker for Mac + Mutagenでの開発も調査する価値があるかと思います。

マイクロサービス開発への対応

場合によっては複数のリポジトリに横断するマイクロサービス開発をDockerのnetwork機能を用いて行っている場合があるかと思います。
この構成ではリポジトリ毎に完全に独立したVMが作成されるという特性上、Docker networkを使った開発が難しくなってしまう可能性があります。

現在マイクロサービスでの開発は行っていないため確認できていないのですが、解決策として

  • 上記Docker for MacへのMutagenの適用
  • 必要なマイクロサービスをsubtree(submodule)として配置したリポジトリを作り、そのリポジトリ上でVMを構築する

というものが使えないかと考えています。
これについては今後マイクロサービス開発が必要になったタイミングで検証を進めていこうかと考えています。

Mutagenの同期セッションについて

vagrant-mutagenでのファイル同期についてですが、 $ vagrant halt を行わずにVMが終了した場合にはセッションが削除されずに残ってしまうという問題が確認できています。
セッションが残っていた場合には次回のVM起動時にセッションが新たに作成され、多重にセッションが貼られたまま残り続けるという状態になってしまいます。
また、残っているセッションは mutagen.yml の設定を読み込まずに古い設定のまま動作し続けるためバグの原因となります(なりました)。

MacのクラッシュやVMの正常な終了が行えなかった際には $ mutagen sync list でセッションを確認し、不要なセッションについては $ mutagen sync terminate {session_id} で削除するようにしてください。

docker-composeの設定について

docker-compose.yml に以下のようにgem用、npm用のvolumeを作成しているのですが、設定に不備があってDocker for Macでパフォーマンスが落ちているなどあればコメントをいただけると助かります。

services:
  rails: &app_base
    volumes:
      - .:/usr/src/app:cached
      - bundle:/usr/local/bundle:cached
  frontend:
    volumes:
      - .:/usr/src/app:cached
      - npm:/usr/local/npm:cached

volumes:
  bundle:
  npm:
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DXを大幅に低下させるDocker for Macを捨ててMac最速のDocker環境を手に入れる

※DXはデジタルトランスフォーメーションではなくてDeveloper Experienceの方です

Macでネイティブでの動作とほぼ同等の速度の安定したDocker環境を手に入れることができたので、その知見について公開します。
ものによりますが、最大10倍程度パフォーマンスの向上が見られました。

Docker for Macは遅い

MacでDockerを使って開発している方は体感していると思うのですが、Docker for Macの速度はネイティブで動かすのと比べて相当遅く、リソースも消費します。
自分はRails + React(webpack) というWebでは比較的一般的なスタックで開発しているのですが、Railsの初期読み込み・webpackの(差分)トランスパイル・yarn installなどが致命的に重く、LinuxでのDocker環境と比較してかなりDXが低下します。
特に大量のファイルの読み込みや、ファイル変更のリアルタイム検知が弱い印象が強いです。

また、Docker for Macは不安定な面もあり、(例えばgitのブランチ変更などで)大量のファイル変更が起こった場合にCPU使用率が100%に張り付いてしまい、再起動しないと戻ってこないなどの現象も起きることがあります。
com.docker.hyperkit 100% cpu usage is back again · Issue #3499 · docker/for-mac

この問題の大部分は調べた限りファイルシステムをマウントした際のオーバーヘッドに起因しており、docker-syncなどを使ってファイルシステムの扱い方を変えることである程度改善することが知られています。
しかし、docker-syncも不安定さが残るなど、懸念点があります。
参考: hanhan's blog - Docker for Macのmount遅い問題まとめ

また、自分は設定の煩雑さから使ったことがないのですが、Docker for MacはNFS Volume sharingに対応しているので、それを使うことでもある程度改善されるのではないかと思います。

この記事ではパフォーマンス低下の最も大きな原因となっているファイルシステムについていくつか改善案を試し、その中で最も効果があったVirtualBox(Ubuntu)を使い、ファイルシステムのマウントを行わずに(VMのネイティブ?ファイルシステム上で)Dockerを使う、という手法について解説しようと思います。

Docker for Macと今回構築する環境(VirtualBox + Docker)の違い

docker.png

基本的にはどちらもVM上でDockerが動作することになります。

Docker for Mac

Docker for Macでは、インストール時にAlpine LinuxベースのHyperkit VMがインストールされ、起動時に /var/run/docker.sock が生成されます。
このVMはDocker for Mac上で自動的に管理され、Mac上で docker, docker-compose コマンドを実行する際に特に設定をしていない限り docker.sock を経由して透過的にVM内でDockerコマンドが実行されるようになっています。

VirtualBox + Docker

一方、今回構築する環境ではVirtualBoxを使って実際にVMを管理することになります。
Vagrantを用いてVirtualBoxでUbuntu環境を構築し、その中でDockerを動作させます。
完全に独立した環境のため、ファイルシステムについては VMに設定しているものがDocker上にマウントされ 、Dockerコマンドについてはsshで接続した上でVM内から実行することになります。
VM内の docker.sock をMacにマウントすることでMac上から透過的にDockerコマンドを扱えるかもしれませんが、まだ検証していません。

ファイルシステムについてですが、Vagrantでファイルを同期する sync_folder 機能で指定できる同期タイプとして

  • VirtualBox(オプション指定なし)
  • NFS
  • rsync
  • SMB

があります。
しかし、今回はどれも使わずに(VM起動時に1度だけrsyncをする)、Vagrantから独立した手段でファイル同期を行います。

実際の速度差

Docker for Mac, VirtualBox + Docker, Macネイティブについて、計測が容易で差が大きかったものについて環境毎の実測値を記載します。
Railsへのアクセスについて、ネイティブよりVMの方が早い理由についてはよく分かっていません。

Command Docker for Mac VirtualBox + Docker Macネイティブ
yarn install 117.84s 16.86s 16.88s 6.8倍高速化
Rails起動後の初回アクセス
(curl)
22.43s 2.199s 3.32s 10倍高速化

VMの設定や使い方

VMの構築・設定

Vagrantを使ってVMを構築します。
実際に使っているVagrantfileの構成は以下です。
CPU・メモリ・ディスクサイズなどは適宜調整してください。

追加でプラグインとして

  • vagrant-disksize
  • vagrant-hostsupdater
  • vagrant-mutagen

が必要になるので、 $ vagrant plugin install vagrant-disksize vagrant-hostsupdater vagrant-mutagen を実行してプラグインをインストールしておいてください。

Vagrant.configure('2') do |config|
  config.vm.box = 'ubuntu/xenial64'

  config.vm.hostname = 'my-app'

  config.vm.network :private_network, ip: '192.168.50.10'

  config.vm.provider :virtualbox do |vb|
    vb.gui = false
    vb.cpus = 4
    vb.memory = 8192
    vb.customize ['modifyvm', :id, '--natdnsproxy1', 'off']
    vb.customize ['modifyvm', :id, '--natdnshostresolver1', 'off']
  end

  config.disksize.size = '30GB'
  config.mutagen.orchestrate = true

  config.vm.synced_folder './', '/home/vagrant/app', type: "rsync",
    rsync_auto: true,
    rsync__exclude: ['.git/', 'node_modules/', 'log/', 'tmp/']

  config.vm.provision 'shell', inline: <<-SHELL
    curl -fsSL https://get.docker.com -o get-docker.sh
    sh get-docker.sh
    usermod -aG docker vagrant

    curl -L "https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
    chmod +x /usr/local/bin/docker-compose
  SHELL
end

VMの使い方

(実際のコマンドの実行はファイル同期の章に書いている内容を行った後にしてください。)
$ vagrant up を実行すると各設定が適用されたVMの作成・起動とrsyncの実行が行われ、 .//home/vagrant/app に同期されます。rsyncの実行は起動時のみで自動的な実行は行われません。
初回はprovisionでDockerとDocker Composeのインストールが行われます。

その後、 $ vagrant ssh をしてVMにSSHでログインし、 $ cd app && docker-compose up {rails} のようなコマンドを実行することでDockerコンテナが立ち上がります。
イメージのビルドなどの処理は必要に応じて適宜行ってください。
立ち上がったアプリケーションにはVagrantfileで指定しているIPでアクセスできます。 (http://192.168.50.10:{3000} など)

ファイル同期の手段

開発を行う際にはローカルとVM上でファイルを同期する必要がありますが、その際にVirtualBoxの標準の共有フォルダやNFSを使うと、VM内からDockerへマウントした際のパフォーマンスが結局大幅に低下します。
そのため、MacからVMへのファイルシステム自体のマウントは行わず、ファイル変更を検知して相互にファイルを転送するような同期手段が望ましいです。

Vagrantのrsyncでの同期がそれに近いのですが、その場合ホストからVMへ単方向の同期しか行われないという問題があります。
具体的にはVM内で $ bundle install$ yarn install を実行した際に更新される Gemfile.lockyarn.lock のようなファイルについてVMからMacへ同期が行われません。

VM内のファイル変更を検知してホストにrsyncを実行するようなコマンド実行も試したのですが、双方向にrsyncで適切な同期を行うというのは実現できませんでした。

ファイル同期のソリューション 「Mutagen

上記の問題についてですが、最終的にMutagenを使うことで解決しました。
今回Vagrantを採用している理由の一つとして、Vagrant PluginでVMに対してMutagenの設定が簡単に行える、というものもあります。
VMの構築・設定の節で既に記載しているのですが、Mutagenをインストールし、 vagrant-mutagen プラグインを導入することで双方向のファイル同期が適切に行われます。
Mutagenのインストールですが、Macでは $ brew install mutagen-io/mutagen/mutagen を実行することで行う事ができます。

その後、Vagrantfileと同じ階層に以下の mutagen.yml を配置してください。

sync:
  app:
    mode: "two-way-resolved"
    alpha: "./"
    beta: "{my-app (Vagrantfileで指定したホスト名)}:/home/vagrant/app"
    ignore:
      vcs: true
      paths:
        - "/node_modules"
        - "/log"
        - "/tmp"

その後 $ vagrant up でVMを起動すると、Mutagenによって双方向のファイル同期が行われます。
この同期は双方向にほぼリアルタイムで行われ、ファイルシステムのマウントではなくファイルの転送で実現されているため、最終的にDockerコンテナへマウントされた際のオーバーヘッドはほぼ発生しません。

結果

結果として現在業務で行っている開発にVagrantとMutagenを導入したことで、Macでも快適にDockerを使った開発が行えるようになりました。
Docker for Macでは速度・リソース・安定性のどの面でも苦労していたのですが、現在はある程度安定したDocker環境を構築できています。
sshしなければ使えないなど多少不便な点も残ってはいるのですが、それを上回るメリットが享受できていると感じています。

Docker for Macでの開発に消耗している方は数多くいる印象なので、もし機会があればこの構成を試してみていただけると幸いです。

備考・注意点

実行環境のスペック

MacBook Pro (16-inch, 2019)
CPU: 2.4GHz 8コア Intel Core i9
メモリ: 64GB 2667MHz DDR4

Docker for MacへのMutagenの適用

MutagenはDocker for Macで扱うこともできるようです。
その際のパフォーマンスは計測できていないのですが、もしかしたらVagrantを経由した場合と同等の速度が出るかもしれません。

今回はMutagen導入時には既にVagrant + rsyncを使った場合の速度が非常に速いことが確認できていたので、設定がシンプルだったvagrant-mutagenを採用したのですが、Docker for Mac + Mutagenでの開発も調査する価値があるかと思います。

マイクロサービス開発への対応

場合によっては複数のリポジトリに横断するマイクロサービス開発をDockerのnetwork機能を用いて行っている場合があるかと思います。
この構成ではリポジトリ毎に完全に独立したVMが作成されるという特性上、Docker networkを使った開発が難しくなってしまう可能性があります。

現在マイクロサービスでの開発は行っていないため確認できていないのですが、解決策として

  • 上記Docker for MacへのMutagenの適用
  • 必要なマイクロサービスをsubtree(submodule)として配置したリポジトリを作り、そのリポジトリ上でVMを構築する

というものが使えないかと考えています。
これについては今後マイクロサービス開発が必要になったタイミングで検証を進めていこうかと考えています。

Mutagenの同期セッションについて

vagrant-mutagenでのファイル同期についてですが、 $ vagrant halt を行わずにVMが終了した場合にはセッションが削除されずに残ってしまうという問題が確認できています。
セッションが残っていた場合には次回のVM起動時にセッションが新たに作成され、多重にセッションが貼られたまま残り続けるという状態になってしまいます。
また、残っているセッションは mutagen.yml の設定を読み込まずに古い設定のまま動作し続けるためバグの原因となります(なりました)。

MacのクラッシュやVMの正常な終了が行えなかった際には $ mutagen sync list でセッションを確認し、不要なセッションについては $ mutagen sync terminate {session_id} で削除するようにしてください。

docker-composeの設定について

docker-compose.yml に以下のようにgem用、npm用のvolumeを作成しているのですが、設定に不備があってDocker for Macでパフォーマンスが落ちているなどあればコメントをいただけると助かります。

services:
  rails:
    volumes:
      - .:/usr/src/app:cached
      - bundle:/usr/local/bundle:cached
  frontend:
    volumes:
      - .:/usr/src/app:cached
      - yarn:/usr/local/yarn:cached

volumes:
  bundle:
  yarn:
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

僕「ドラ◯もん〜!Qiitaのトレンド記事を自動で取得したいよぉ〜〜!」

しょうがないなぁ僕くんは〜。

タリラタッタラ~!
ドラ◯もん「スクレイピングぅ〜!」
僕「スクレイピング?」

ドラ◯もん「この技術があれば、Webページの欲しい情報を取得できるんだぁ。」
僕「えっ!?すごいや!早くつかわs」
ドラ◯もん「今回は僕くんが作るんだよ。」

僕「...えっ?」
僕「な、なんで僕が作らないといけないんだ...?だって、そこに既製品があるじゃn」
ドラ◯もん「嫌ならこの話はなかったことにします。」
僕「よ〜し!!やる気がみなぎってきた!!!!」

・・・

僕「とりあえず、Atom入れて、Python3も入れたよ!」
ドラ◯もん「よし、そうしたら早速ソースを書いてみよう」

目的

SlackBotを用いて、Qiitaのトレンド記事を動的取得するよ。

事前準備

Slackのアカウント, およびSlackBotを作っておくこと。
Herokuのアカウントを作っておくこと。
ChromeDriverをインストールしておくこと。
Chromeブラウザがない人は、インストールしておくこと。

上記4点は別サイトさんを参考にしてね。

環境

  • Mac
  • Atom
  • Python3.7
  • selenium
  • ChromeDriver
  • Heroku
  • Slack
  • Chrome
  • Git

・・・

ドラ◯もん「と、その前に。」
ドラ◯もん「スクレイピングする際の注意点〜!」
僕「注意点?」
ドラ◯もん「そう、そもそもにスクレイピングは、どういう原理で行うか知っているかい?」
僕「えっと、プログラムが動いて、対象のWebページにアクセスして、」
ドラ◯もん「そう!Webページにアクセスしないといけない。アクセスすると、当然サーバに(大なり小なり)負荷がかかるよね?」
僕「ウンウン。」
ドラ◯もん「つまり、やたらめったらスクレイピングをしてしまうと、以下のようなことが起こる可能性があるんだ。」

  1. スクレイピングでめちゃくちゃアクセスする。
  2. サーバの負荷がかかる。(閾値を超える)
  3. サービスが停止する。
  4. スクレイピングをした人に対して損害賠償が発生する(可能性がある)

ドラ◯もん(君はここで、「またまたぁ、大袈裟なんだからぁ。」という)
僕「またまたぁ、大袈裟なんだからぁ。」

https://docs.pyq.jp/column/crawler.html

ドラ◯もん「上にリンク貼ったから、ちゃんと目を通しておくんだよ。」
僕「わ、わかったよぉ。」
・・・

プロジェクトツリー

./slack-bot-scraping
├── Procfile
├── __pycache__
├── plugins
│   ├── __init__.py
│   ├── __pycache__
│   └── my_slackbot.py
├── requirements.txt
├── run.py
└── slackbot_settings.py

作っていこう!

・・・

僕「ええと、まずは、run.pyに、こう記述して、よし、これでどう?」

run.py
from slackbot.bot import Bot

def main():
    bot = Bot()
    bot.run()

if __name__ == "__main__":
    print("slack bot start")
    main()

ドラ◯もん「うん、いいと思うよ。」
ドラ◯もん「今回はHerokuにソースをデプロイして、動かすからそっちの準備もしよう。」

僕「どんなファイルが必要になってくるの?」
ドラ◯もん「主に3つのファイルが必要だよ。」

  1. Procfile
  2. slackbot_settings.py
  3. requirements.txt

ドラ◯もん「1つずつ軽く紹介しようか。」

ProcFile

worker: python3 run.py # run.pyをpython3で動かすよって記述

slackbot_settings.py

slackbot_settings.py
API_TOKEN = "XXXX"      # 使用したいAPIのトークン値
DEFAULT_REPLY = "Title" # タイトル(適当で良いと思う)
PLUGINS = ["plugins"]   # プラグインとして読込たいディレクトリに指定

requirements.txt

requirements.txt
pythonで使用したいパッケージが記述されてるよ
選定するのが面倒なら、インストールしているパッケージを全部書いちゃうのをオススメするよ
package_name==version

absl-py==0.7.1
astor==0.8.0
beautifulsoup4==4.7.1
certifi==2019.6.16
chardet==3.0.4
・
・
・

ドラ◯もん「上記3点のファイルを作成できたら、次に処理のソースを書いてみよう。」
僕「おー!」

plugins/my_slackbot.py

plugins/my_slackbot.py
from slackbot.bot import respond_to
from slackbot.bot import listen_to
from slackbot.bot import default_reply
from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

import random
import sys
import os

class Scrapy(object):
    def __init__(self):
        options = webdriver.ChromeOptions()
        options.binary_location = 'Chromeアプリのパスを指定してね'
        options.add_argument('--headless')

        self.driver = webdriver.Chrome(options=options)

        self.result = []

    def login(self):
        login = "https://qiita.com/login?redirect_to=%2F"
        user = os.environ['USER']
        password = os.environ['PASSWORD']

        self.driver.get(login)
        attribute = self.driver.find_element_by_name("identity")
        attribute.clear()
        attribute.send_keys(user)

        attribute = self.driver.find_element_by_name("password")
        attribute.clear()
        attribute.send_keys(password)

        self.driver.find_element_by_name("commit").click()

        return True

    def get_trend_data(self, url):
        self.driver.get(url)
        WebDriverWait(self.driver, 5).until(EC.presence_of_element_located)
        trends = self.driver.find_elements_by_class_name("tr-Item_body")
        for trend in trends:
            data = []
            items = trend.find_elements_by_tag_name("a")
            href = items[0].get_attribute("href")
            title = items[0].text
            data.append(title)
            data.append(href)

            self.result.append(data)

        self.driver.quit()

        return self.result

def common(url, message):
    item_list = []
    sc = Scrapy()
    sc.login()
    results = sc.get_trend_data(url)
    for v in results:
        for item in v:
            item_list.append(item)

    for item in item_list:
        message.reply(item)

@default_reply()
def default(message):
    message.reply("Qiitaのトレンド情報を取得するよ。")
    message.reply("「1日」のトレンドは、「1日」「一日」「いちにち」で取得できるよ。")
    message.reply("「週間」のトレンドは、「週間」「しゅうかん」で取得できるよ。")
    message.reply("「月間」のトレンドは、「月間」「げっかん」で取得できるよ。")

@respond_to('1日*')
@respond_to('一日*')
@respond_to('いちにち*')
def day(message):
    url = "https://qiita.com"
    common(url, message)

@respond_to('週間*')
@respond_to('しゅうかん*')
def week(message):
    url = "https://qiita.com/?scope=weekly"
    common(url, message)

@respond_to('月間*')
@respond_to('げっかん*')
def mon(message):
    url = "https://qiita.com/?scope=monthly"
    common(url, message)

僕「できた〜〜〜!」
ドラ◯もん「めげないでよく作ったね!えらい!」

ドラ◯もん「長くなっちゃったから小分けして説明していこうか。」

class Scrapy(object):

plugins/my_slackbot.py
class Scrapy(object):
    # ここでは使用するChromeDriverの設定を行なっているよ
    def __init__(self):
        options = webdriver.ChromeOptions()
        options.binary_location = 'Chromeアプリのパスを指定してね'
        # ここではハンドレスの設定をしているよ
        # この設定をしないで実行すると、Chromeブラウザが立ち上がって、処理を実行してしまうから気をつけてね
        options.add_argument('--headless')

        self.driver = webdriver.Chrome(options=options)

        self.result = []

    # Qiitaにログインする処理だよ
    def login(self):
        login = "https://qiita.com/login?redirect_to=%2F"
        # user, passwordはHeroku内でexportして使用可能にしているよ
        # 面倒ならここに直書きでもいいんじゃまいかな〜
        user = os.environ['USER']
        password = os.environ['PASSWORD']

        # ログイン画面にアクセスするよ
        self.driver.get(login)
        # "id=identity"の値を取得するよ
        attribute = self.driver.find_element_by_name("identity")
        # 取得した値を一度初期化するよ
        attribute.clear()
        # 初期化した値に`user`を埋め込むよ
        attribute.send_keys(user)

        # "id=password"の値を取得するよ
        attribute = self.driver.find_element_by_name("password")
        # 取得した値を一度初期化するよ
        attribute.clear()
        # 初期化した値に`password`を埋め込むよ
        attribute.send_keys(password)

        # name="commit"の値を取得して、クリックするよ
        self.driver.find_element_by_name("commit").click()

        # ログイン完了
        return True

    def get_trend_data(self, url):
        self.driver.get(url)
        # 5秒間処理を停止するよ
        WebDriverWait(self.driver, 5).until(EC.presence_of_element_located)
        # tr-Item_bodyという要素を取得するよ
        trends = self.driver.find_elements_by_class_name("tr-Item_body")
        for trend in trends:
            data = []
            # aタグを取得するよ
            items = trend.find_elements_by_tag_name("a")
            # この"href"がQiitaのトレンド記事のURLを示しているよ
            href = items[0].get_attribute("href")
            # 0番目に格納されている文字列を取得するよ(タイトル)
            title = items[0].text
            # 取得した各種情報をリストに格納してね
            data.append(title)
            data.append(href)
            # 記事のタイトル, トレンド記事のURLを格納した配列の出来上がり
            self.result.append(data)

        # Driverを閉じるよ
        self.driver.quit()
        # 取得結果を返すよ
        return self.result

def common(url, message):

plugins/my_slackbot.py
def common(url, message):
    # 結果を格納するリストを宣言するよ
    item_list = []
    # Scrapyオブジェクトを呼び出すよ
    sc = Scrapy()
    # ログイン処理の実行をするよ
    sc.login()
    results = sc.get_trend_data(url)
    # 2次元配列になっているので、展開してリスト(1次元配列)にするよ
    for v in results:
        for item in v:
            item_list.append(item)

    # listから情報を一つずつ取り出して、送信するよ
    for item in item_list:
        message.reply(item)

def default(message):

plugins/my_slackbot.py
# respond_toに該当しないメッセージが来たら、以下の内容をBotが送信してくれるよ
@default_reply()
def default(message):
    message.reply("Qiitaのトレンド情報を取得するよ。")
    message.reply("「1日」のトレンドは、「1日」「一日」「いちにち」で取得できるよ。")
    message.reply("「週間」のトレンドは、「週間」「しゅうかん」で取得できるよ。")
    message.reply("「月間」のトレンドは、「月間」「げっかん」で取得できるよ。")

# 1日, 一日, いちにち, から始まるメッセージを送信したら、Botは以下の処理を行うよ
@respond_to('1日*')
@respond_to('一日*')
@respond_to('いちにち*')
def day(message):
    url = "https://qiita.com"
    common(url, message)

# 週間, しゅうかん, から始まるメッセージを送信したら、Botは以下の処理を行うよ
@respond_to('週間*')
@respond_to('しゅうかん*')
def week(message):
    url = "https://qiita.com/?scope=weekly"
    common(url, message)

# 月間, げっかん, から始まるメッセージを送信したら、Botは以下の処理を行うよ
@respond_to('月間*')
@respond_to('げっかん*')
def mon(message):
    url = "https://qiita.com/?scope=monthly"
    common(url, message)

フロー

UserメッセージをBotに送信。
|
Userメッセージに該当キーワードはある?。
| |
y n
| |
| def default(message); 処理を行う。
|
|
ChromeDriverの設定をする。
|
Qiitaにログインする。
|
Qiitaのトレンド情報を取得する。
|
取得したトレンド情報をBotが送信する。

ドラ◯もん「あとはこのソースをHerokuにデプロイして、SlackBotがメッセージを送信してくれたら完成だ!」

Heroku下準備

ドラ◯もん「以下のURLにアクセスして、パッケージをダウンロードしよう!」
https://devcenter.heroku.com/articles/getting-started-with-python#set-up

ドラ◯もん「パッケージを展開したら、CLI上で、heroku login としよう。」

ドラ◯もん「そうすると、
heroku: Press any key to open up the browser to login or q to exit:
と表示されるので、q以外のキーを叩こう。」

ドラ◯もん「叩いたら、ブラウザに遷移して、Herokuへのログインを求められるよ。メールアドレスと、パスワードを入力して、ログインしよう。」

ドラ◯もん「CLI上では、
Logged in as メールアドレス
と表示されたら成功。」

ドラ◯もん「これで、HerokuとローカルPCとの連携ができたね。」

Gitリポジトリの初期化

ドラ◯もん(カレントデュレクトリはデプロイしたいディレクトリに指定しておこうね。)

git init
git add .
git commit -m "new files"

Herokuにアプリを作成しよう

ドラ◯もん「heroku create アプリの名前 コマンドを実行して、アプリを作成しよう!」
ドラ◯もん「heroku list コマンドを実行して、アプリの名前 があるのを確認してね。」

ドラ◯もん「git push heroku master コマンドを実行すると、PythonのソースコードがHerokuにプッシュされたよ。」

メッセージを投げてみよう

スクリーンショット 2020-01-16 15.18.26.png
僕「お〜〜〜!」
僕「一日」

F4697DCA-4958-4EC1-B029-ECDBA1EEB6F0_1_102_o.jpeg

僕「これで現在の1日のトレンドが取得できるのか〜!」
ドラ◯もん「週間、月間も同様に取得できるけど、今回は尺の都合でカットするよ。」

まとめ

Python3.7を用いて、特定のページから欲しい情報を取得することができました。
各サイトの規約や、サーバに対しての負荷も考慮してソースを組みましょう。
あまりにも負荷をかけたらDos攻撃とみなされ、処罰される可能性があります。
ぜひ、気をつけましょう。

頭の中の処理をそのまま記述したソースですが、
改善案や、指摘事項ありましたらコメントください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む