Tech Beans

Web企業で働くエンジニアのBlog


Rundeck で履歴を定期パージするためのツール

Rundeck はジョブヒストリーを定期自動パージできないという問題があり、 いつの間にか数万エントリーになっていたりするので、作りました、という告知

github.com

pyenv + venv + direnv で作るPython3環境

pyenv + virtualenvの問題点

Pythonでの開発環境において、

  • 開発ホスト上に複数のバージョンのPythonをインストールしたい場合 … pyenv
  • プロジェクトごとに仮想環境を構築し、モジュールを切り替えたい場合 … virtualenv

の2つの組み合わせは一般的に使われているものかと思います。

virtualenvにおける、仮想環境切り替えの煩わしさ

virtualenvで開発環境を構築した場合、仮想環境 を切り替えるためには、プロジェクトのディレクトリ配下で、毎回 source ./{virtualenv directory}/bin/activate しなければなりません。 この、仮想環境の切り替えを手動で行うのは開発中なかなか面倒なのと、いろいろなプロジェクトを触っているとついつい忘れがちになります。

pyenv + pyenv-virtualenvにするか?

pyenv と pyenv-virtualenv(プラグイン) を使えば、プロジェクトディレクトリにcdしたときに仮想環境を自動で切り替えてくれるので、手動による煩わしさはなくなります。

しかしながら、pyenvが管理するバージョンの中に、Python自体のバージョンと、virtualenvによる仮想環境が混在する状態になってしまいます。 プロジェクトが少ない段階ではこれでも十分なのですが、プロジェクトが増えてきた場合に、非常に管理しづらくなってきます。

このように、基本的な仮想環境の管理はvirtualenv方式がよいのですが、その仮想環境の切り替えは自動でやりたい、という場合には、direnvを使うとよさそうです。

そこで今回、 pyenv + venv(virtualenv) + direnv を使って、Python3開発環境を構築してみました。

pyenv のインストール

公式サイト の手順でインストール

今回、venvを使うのでPython3 を入れておきます。

pyenv install 3.6.1 # versionは3.xのものを適宜

# 適当なテスト用プロジェクトディレクトリを作成
mkdir ~/myproject
cd ~/myproject
pyenv local 3.6.1
pyvenv my_venv

direnv

direnvはディレクトリごとに環境変数を切り替えることができるツールで、特定のディレクトリにいるときだけ環境変数をsetし、ディレクトリから抜けるときにunsetということをすべて自動的に行ってくれます。

Qiitaにも載っています。

qiita.com

direnv のインストール

インストールは非常に簡単です。

git clone http://github.com/zimbatm/direnv
cd direnv
sudo make install

また、以下の内容を.zshrcに追加します。

eval "$(direnv hook zsh)"
show_virtual_env() {
  if [ -n "$VIRTUAL_ENV" ]; then
    echo "($(basename $VIRTUAL_ENV))"
  fi
}

PS1='$(show_virtual_env)'$PS1

2行目以下は、direnvを使った場合にそのままでは仮想環境名がプロンプトに表示されないのですが、 その対策のために必要になります。

direnvの使い方

環境変数を切り替えたいディレクトリ、ここでは開発プロジェクトのディレクトリ配下に.envrcというファイルを作成します。 direnvは、cd先のディレクトリ配下にこの.envrcスクリプトが存在する場合にそのスクリプトbashで実行し、スクリプト内でexportされた環境変数を、現在のシェルにsetします。

$ cat ./.envrc
HOGE="fuga"
export HOGE # この変数がディレクトリ以下でのみ有効になる

また、このディレクトリから更に別のディレクトリに移動した場合には、setされた環境変数は自動でunsetされます。

この機能を利用して、venvの開発環境の自動切り替えを実現させます。

.envrc

.envrcには以下のようにして保存します

source ./my_venv/bin/activate

これだけで準備完了です。

仮想環境の自動切り替え

以上の準備ができたらシェルを再起動して試してみます。

$ cd ~/myproject                                                                 
direnv: loading .envrc
direnv: export +VIRTUAL_ENV ~PATH
(my_venv)
$

$ which pip                                                                          
~/myproject/my_venv/bin/pip

自動で仮想環境が有効になりました。

direnv: error .envrc is blocked. Run 'direnv allow' to approve its content. というエラーが出た場合は direnv allow .を実行します。

また、ディレクトリから抜けると、仮想環境が自動的に無効になることもわかります。

$ cd ~/
direnv: unloading

$ which pip
~/.pyenv/shims/pip

仮想環境の自動切り替えが実現できました! これでプロジェクトが増えても安心できそうです。

また、今回pyvenvを使いましたが、virtualenvでも同様にできるはずですので、Python2環境にも応用できるかと思います。

Elasticsearch 5.xの新機能と、1.xから5.xへのアップグレード


この記事は、Elastic stack Advent Calendar 2016の11日目の記事です。

Elasticsearch クラスタを1.7 から 5.0.2まで上げたときに、それなりに変更点が多く、苦労しました。

本記事では、そのときのノウハウを元に、

  • 1.xを使っていて、5.xに上げたいと思っている

人を主な対象に、アップグレードの注意点や、1.xからの追加・変更機能について書きたいと思います。

世の中的にすでに2.xの人が多いと思いますが、数ヶ月前にElasticsearch勉強会に参加した際に、まだ1.xを利用している人がそれなりにいたので、今でもまだ役に立つかなと思います。

とはいえ ver2.xからの変更点も含むので、今2.xを使っている人にも参考になるように書きます。

モチベーション

  • Reindex APIが使いたかった。
  • Kibanaでダッシュボードの共有URL(shorten url)が使いたかった
  • いい加減、上げたかった 技術的な興味

5.xで追加・変更される機能について

Elasticsearch

公式より個人的にうれしかったものから

  • バリデーション強化

IngestNodeなどが注目されるところですが、個人的にはまずいろいろなAPIのバリデーションが強化されており、何か間違っていてもすぐに分かるようになったというのが大きいです。

  • Reindex API
    • 2.xからありますが、クラスタからも取り込めるようになったみたいです。アップグレード時に大活躍してくれます(後述)
  • フィールド名にドットがまた使えるようになった。(後述)
  • Ingest Node
    • 目玉機能かと思いますが、まだプロダクトで使ってません。すでに他の記事があるので割愛
  • Indexing パフォーマンス改良
  • ドキュメントの類似度算出アルゴリズムがTFIDFからBM25に変更
  • Cluster Allocation Explain API
    • これまで運用している中で、度々「なぜかシャードアロケーションが進まない」といった出来事があり、よくよく調べてみるとdisk watermarkに引っかかっていたなど、わかりにくい状況が多かったので、その理由を詳細にレポートしてくれるAPIは便利そうです。
  • 独自のスクリプト言語Painlessの導入
  • search_after
    • ページング処理が楽にかけるようになりそう。そのうち別に記事を書きたいです。

Kibana

  • shorten URL
    • モチベーションの1つ
    • https://github.com/elastic/kibana/issues/1553
    • ログ監視していて、通知にこれをつけておくと超捗る
    • 公開短縮URLサービスを使えない場合も多いはず。
    • (追記 2016/12/12)
      • こちらの機能はすでにKibana4.4から導入されているので、Kibana5の機能ではありません。ES 2.2以上を使っていればKibana4.4も使えるので問題ないですね。 今回ES1.x からの差分として記載しています。ご指摘ありがとうございます。

f:id:soy_msk:20161211021259p:plain

  • Sense ( => Console ) がデフォルトで入ってる。

この他にも_cat/**APIが追加になっていたり、運用で触っていると、ところどころ使いやすくなっているなと感じます。起動時にカーネル設定もみてくれて、推奨値でない場合は警告を出してくれます。

ということで、機能というより、運用のしやすさが上がったと個人的には感じています。

残念だったところ

総じてよかったのですが、残念なところもあります。

  • head, HQと言ったWebUIを持つプラグインがことごとく使えない
    • Elasticsearch側の仕様変更によるものみたいですが、ヘビーユースしていただけに辛いです。Webサーバーとして別個に構築する方法や、そもそもkibanaにx-packいれてmarvelに乗り換えたりと、方法はいろいろありますが、コマンド一発でインストール&どのノードでもアクセス可能な魅力にはかなわず。
    • headに関して、Kibana pluginとして開発し直す話もあるようですが。。

5.xへのアップグレード

続いて、今回1.xからアップグレードする過程で注意する点やtipsをまとめます。

メジャーバージョン間でのアップグレードなので、ローリングアップグレードはできません。


JVM

java8必須になっています

また、JVMオプションの設定については これまで bin/elasticsearch.in.shなどに記述していましたが、conf/jvm.optionsという別ファイル切り出して記述することが推奨されています。

-Xms2g 
-Xmx2g




設定に関する変更

index.*** の設定

これまで config/elasticsearch.yml に書いていたindex.xxxの項目がかけなくなりました。

代わりにindex mappingに全てまとめられたようです。 例えば、今後作成するindexのデフォルト設定を変更したい場合は、index templateを利用すればよいかとおもいます。

PUT /_template/default_template
{
  "order":0,
  "template": "*",
  "settings": {
    "index": {
      "number_of_shards": "4",
      "number_of_replicas" : "2"
    }
  }
}




Index Mappingに関する変更

1.xからだとかなり変更があります

String 型の廃止

これまで文字列データに対してString型があり、analyzed, not_analyzedオプションで検索可能フィールドかどうか設定していましたが、5.xからはtext, keywordという新しい2つの文字列型が導入され、明確に区別しています。

文字列型のdynamic mappingがtextkeywordのマルチフィールドになっているので、何も考えずにデータを入れると、両方のフィールドができ、検索も可能になります。

それはいいのですが、容量もその分増える ことを考えると、データ量の多いフィールドなどはやはり事前にマッピング定義しておいたほうが無難かと思います。


インデックス内で同じ名前を持つフィールドは同じ型にしなければならない制約

これはtypeをまたいだフィールドにも適用されるので、

  • index1.type1.name
  • index1.type2.name

というフィールドは同じ型にしなければならないという制約が入っています。


'ドット'をフィールド名に持つフィールドの扱い

これは紆余曲折がありまして、2.xでドットフィールドが使えなくなりましたが、5.xでサポートが復活しています。

ただし まったく同じというわけではなく、ドットでつながったフィールドは内部的にハッシュオブジェクトとして管理されているため、例えば

A.B.C
A: object type
B: object type
C: *

となりますので、Aを別のフィールド名につかっているとNGです。

user: 'john' # keyword型としてまず登録
user.age: 10 # => userをobject型として使っているのでError

フィールド名に'ドット(.)' をが含まれるフィールドがある場合には注意が必要です。


クエリに関する変更

ver2.xで非推奨になったものも5.xでは廃止になっているので対応が必須です。

ファセットの廃止

これは2.xですでに廃止されているので今更ですが、1.xからの人には念のため。

1.7でもaggregationが使えるので、書き換えておきましょう。 aggregationの方が書きやすいですね。

filteredクエリの廃止

2.xでdeprecatedになっていたものが廃止になっています。 boolクエリに書き換えます

# ver1.x
"query": {
    "filtered": {
        "query": {"should": []},
        "filter": {}
    }
}
 
# ver5.x
"query": {
    "bool": {
        "should": [],
        "filter": {}
    }
}

他にも細々ありますが、問題になったのは上2つでした。

データ移行には Reindex APIを活用する

アップグレードに際し、自分は5.xのクラスタを新たに構築し、順次移行していく手順を取りました。

その際に困るのがデータ移行ですが、5.xになってリモートからの取り込み機能が追加されています。

これが超便利

POST _reindex
{
  "source": {
    "remote": {
      "host": "http://old-cluster-node:9200"
    },
    "index": "logstash-2016.10",
    "size": 1000 # chunk size
  },
  "script": {
    "inline": "ctx._source.put('new_field', ctx._source.remove('old_field'))"
  },
  "size": 100000 # total reindex docs
  "dest": {
    "index": "logstash-2016.10"
  }
}

これだけでデータコピーができてしまいます。簡単ですね。

さらにデータ移行と同時に、

  • script でフィールド名を書き換える
  • 1回あたりのコピーサイズを制限する

も行っています。

ただし、コピー元となるノードを予めコピー先クラスタの設定内に指定しておかないといけません。

# config/elasticsearch.yml
reindex.remote.whitelist: old-cluster-node:9200 

Kibanaのダッシュボード移行

これは元々Kibana4を使っていたので export/import機能を使えば直ぐにできました。 f:id:soy_msk:20161211015027p:plain

migration plugin

アップグレードする前にインストールしておくと、アップグレード後に非推奨、廃止になる機能を使っているインデックスに対してはWebUIで警告を出してくれ、どうすればよいかアドバイスしてくれます。

# install
$ ./bin/plugin -i migration -u https://github.com/elastic/elasticsearch-migration/releases/download/v1.18/elasticsearch-migration-1.18.zip

http://localhost:9200/_plugin/migration/ にアクセスすると診断してくれます。

試しにindex1.type1 にドット付きフィールドを入れてみました。 f:id:soy_msk:20161211015015p:plain

ただし、1.x => 2.x , 2.x => 5.x とプラグインが分かれていて、直接1.x => 5.xのmigrationをみてくれるわけではないので注意。

実際Reindex APIでデータ投入しちゃったほうが確実でした。

まとめ

まだメジャーバージョンが出たばかりというところですが、 プロダクションで使えるかという点においては、少なくともバックエンド的な使い方では十分使えそうという印象でした。

ただこの記事執筆時点ですでに5.1.1までリリースされており、そのリリースサイクルを見ると、今後も頻繁にアップグレードしていく必要はありそうです。

strongSwan でVPN(IKEv2) を 構築する Part.2 ~ MTU値の調整 ~

特定のサイトにつながらない

前記事で設定したVPNサーバーを経由した場合に、特定の一部サイト(github.com, yahoo.com)にアクセスできないことがわかった。

現象としては、

  • ping は通るが、80ポートの返答がない
  • https, httpなどプロトコルにはよらない
  • VPNサーバーとして動かしているLinux(CentOS)上からはアクセスできる。

tracerouteの結果は、最初の数段は通るが、以降はtimeoutしてしまっていた。

これもかなりハマってしまったが、原因を探してみると、どうやら経路間のMTU値の設定がVPNを経由した場合にうまくいかないようだ。

PMTUD

MTUはMaximum Transmission Unitの略で、1フレームごとに送信するデータの最大値を意味する。 MTUは現在ではデフォルト1500になっていることが多いが、経路によってはこの値が異なる場合がある。

自宅にはNTT東日本FTTHを引いているが、PPPoE経路上では追加のヘッダーが付与される関係上この値が小さくなる。 実際に自宅のルーターの設定を覗いてみるとMTU= 1454バイトに設定されていた。

通信の際には、経路上で最小のMTU値に合わせてデータを送信する必要がある。 もし、最小MTU値を超えたフレームを送信した場合、フレームを分割送信するフラグメンテーションを行って再送する必要がある。(というICMPパケットが送信先から返ってくるらしい。あまり詳しくない。。)

で、このフラグメンテーションはパフォーマンス上大変よろしくないので、できれば最小MTU値で送信してフラグメンテーションしないようにしたい。 ところが、この最小MTU値は、接続先までの経路によって異なり、固定ではない。

そこで、その最小MTU値を決定するために、PMTUD(Path MTU Discovery)がある。 PMTUDの詳細についてはググれば出てくるので割愛。

ウェブサイトへのアクセスが失敗する理由

さて、問題はここから。

PMTUDはICMPパケットを利用するため、一部の経路でこれが遮断されていると使えない。 ICMPは本来遮断すべきではないのだが、pingを塞ごうとしてICMPまるごと塞いでしまっている経路が存在する。

その場合、フラグメンテーションによって解決されるわけだが、そこに今回のVPN特有の問題がある。

参考サイト

によると、

Instead of fragmenting a too-large IP packet, the VPN server is told (through the Don’t Fragment (DF) flag in the IP header of the sender) to discard the packet and reply with an ICMP fragmentation required (type3, subtype 4) message.

本来、アクセスしたいウェブサーバーが、このMTU値を超えた通信を行ってきた場合に、VPNサーバーはそのパケットを破棄し、代わりにICMP fragmentation required を返す。

When the sender receives this ICMP packet, it learns to use a smaller MTU for packets sent to our VPN server. In theory. In reality, many websites (senders like www.yahoo.com) stupidly implement ICMP filters that break PMTUD functionality.

本来であれば、このパケットを受け取ったサーバーはMTU値を調整して再送してくれるはず(google.comなど)だが、一部のサーバーではこれがうまく実装されておらず、延々とack待ちになってしまう。 この実装の違いが、VPNを通した場合にアクセスできたりできなかったりするサーバーの違いだ。

結果VPNクライアントからみると通信がtimeoutしてしまうわけだが、まさに発生している現象とそっくりだ!

実際、VPNサーバー上でtcpdumpを取ってみると、

21:38:09.594323 IP {VPNサーバー} > {接続先IP}: ICMP {VPNサーバー} unreachable - need to frag (mtu 1374), length 556

というようなログが出ている。やはりMTU値が問題なようだ。

CentOS7 firewalldでのMTU値の調整

対応として、参考サイトにはiptablesの設定がのっている。

$ iptables -t mangle -A FORWARD -o eth0 \
 -p tcp -m tcp --tcp-flags SYN,RST SYN \
 -s 192.168.12.0/24 \
 -m tcpmss --mss 1361:1536 \
 -j TCPMSS --set-mss 1360

$ echo 1 >/proc/sys/net/ipv4/ip_no_pmtu_disc
  • 1行目

    • -s 192.168.12.0/24 はクライアントのソースIPで条件指定している。ここでは前回設定したVPNクライアントのサブネットを指定
    • --mss 1361:1536 でMSSが1361 ~ 1536の間にあるパケットだけ絞って設定を適用できる。
  • 2行目

    • UDPのための設定

今回はCentOS7上での設定となるため、これを書き換えて以下のコマンドを実行した。

CentOS7, Firewalld

# firewall-cmd --direct --permanent \
--add-rule ipv4 mangle FORWARD 1 \
-p tcp -m tcp --tcp-flags SYN,RST SYN \
-s 192.168.12.0/24 \
-m tcpmss --mss 1301:1536  \
-j TCPMSS --set-mss 1300
# echo 1 >/proc/sys/net/ipv4/ip_no_pmtu_disc
# systemctl restart firewalld

MSSが1360では自分の環境では動作しなかったので、1300にしている

再起動が終わった後に試してみると、

ちゃんとVPNクライアントからgithub.com, yahoo.comにアクセスできるようになった!

長かった!

ちなみに

こちらのQiitaなどで紹介されている

 firewall-cmd --direct --add-passthrough ipv4 -I FORWARD -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

は上手くいかなかった。どうやらVPNクライアントでは上手く動作しないようだ。

strongSwan でVPN(IKEv2) を 構築する Part.1

基本的な手順は参考元サイトをもとに構築していく。

  • 想定クライアント

※今回、最終的にユーザー名・パスワード認証は成功しているが、公開鍵認証はiOS, OS Xのネイティブクライアントで動作できていない。 OSX側の問題の可能性もあるので、解決したら記事更新予定。

VPN方式の違いについては、前回記事でまとめている。 soymsk.hatenablog.com

乱数精度の確認

参考:

証明書発行で利用する乱数の精度を確認する。

ホスト上のエントロピーは下記のコマンドで確認できる。 以下の値が1000以上なら問題ないらしい。

# cat /proc/sys/kernel/random/entropy_avail
66

足りてない。。

# yum install rng-tools
#  cat /dev/random | rngtest  -c 1000

乱数生成が遅いのか、上記コマンドも全く返ってこない。

乱数用のエントロピーが低すぎる状態であることはわかったので、havegedをインストールし、エントロピーの生成を増やしてみる。 (下記はVPNの構築については必須ではない)

haveged

havagedのセットアップ

$ sudo yum install haveged
$ sudo systemctl enable haveged
$ sudo systemctl start haveged

$ sudo cat /proc/sys/kernel/random/entropy_avail
2273

これで、エントロピーが1000を超えて生成されるようになった。

Install Strongswan

StrongswanをインストールはyumでOK

$ sudo yum install strongswan

認証鍵の作成

つづいて、IKEv2で使うサーバー側のCA認証鍵の生成を行う。 認証鍵は、strongswanのpkiサブコマンドで作成できる。

ルート証明書の作成

自己署名のルート証明書(いわゆるオレオレ証明書)を作成していく。

自己署名のルート証明書について

今回は自己署名のルート証明書を作成したが、 VPN接続の前に、クライアントにルート証明書をインストールする必要がある。

公的なCAから証明書発行が得られるのであれば、クライアントにルート証明書をインストールする手間が不要になるのでそちらがオススメ。

まずはルートサーバーの秘密鍵を作成する。

# cd /etc/strongswan
# strongswan pki --gen --type rsa --size 4096 --outform der > ipsec.d/private/strongswanKey.der
# chmod 600 ipsec.d/private/strongswanKey.der

続いて、ルート証明書を作成。(dnの中身は適宜書き換えること)

# strongswan pki --self --ca --lifetime 3650 --in ipsec.d/private/strongswanKey.der --type rsa --dn "C=JP, O=soymsk, CN=strongSwan Root CA" --outform der > ipsec.d/cacerts/strongswanCert.der

# 作成したルート証明書の内容確認
# strongswan  pki --print --in ipsec.d/cacerts/strongswanCert.der
  subject:  "C=JP, O=soymsk, CN=strongSwan Root CA"
  issuer:   "C=JP, O=soymsk, CN=strongSwan Root CA"
  validity:  not before Oct 07 22:41:11 2016, ok
  .....

VPNサーバーの認証鍵の作成

VPNサーバー自身の秘密鍵を作成

# strongswan pki --gen --type rsa --size 2048 --outform der > ipsec.d/private/vpnHostKey.der
# chmod 600 ipsec.d/private/vpnHostKey.der

続いて、作成したルート証明書秘密鍵から、公開鍵を生成する。

  • CNと--sanにはドメイン名あるいはIPアドレスを指定できるが、これはVPNサーバーのものである必要があるので注意
  • --flag に以下の設定を入れている。
    • Windowsのクライアントからアクセスする場合には、--flag serverAuthが必要。
    • OS X 10.7.3以前のOSXでは、--flag ikeIntermediateが必要。
# strongswan pki --pub --in ipsec.d/private/vpnHostKey.der --type rsa | strongswan pki --issue --lifetime 730 --cacert ipsec.d/cacerts/strongswanCert.der --cakey ipsec.d/private/strongswanKey.der --dn "C=JP, O=soymsk, CN=soymsk.example.jp" --san soymsk.example.jp --flag serverAuth --flag ikeIntermediate --outform der > ipsec.d/certs/vpnHostCert.der

# strongswan --issue --help
# --cacert: CA certificate file.  作成したルート証明書のパス
# --cakey: CA private key file. 作成したルート秘密鍵のパス

作成したサーバー公開鍵は、以下のコマンドで詳細を確認できる。

# strongswan pki --print --in ipsec.d/certs/vpnHostCert.der
  subject:  "C=JP, O=soymsk, CN=soymsk.example.jp"
  issuer:   "C=JP, O=soymsk, CN=strongSwan Root CA"
  validity:  not before Oct 08 08:06:09 2016, ok
             not after  Oct 08 08:06:09 2018, ok (expires in 729 days)
  ......

# OpenSSLを使っても確認できる
# openssl x509 -inform DER -in ipsec.d/certs/vpnHostCert.der -noout -text

クライアントの認証鍵を作成

ルート証明書を使って、今度はクライアントの認証鍵を発行する。 やり方はサーバー認証鍵のものと同じような流れになるが、CNの部分がユーザー名@ドメインの形になる。

秘密鍵の作成

# cd /etc/strongswan/
# strongswan pki --gen --type rsa --size 2048 --outform der > ipsec.d/private/vpnClientKey.der
# chmod 600 ipsec.d/private/vpnClientKey.der

公開鍵の作成

# strongswan pki --pub --in ipsec.d/private/vpnClientKey.der --type rsa | \
 strongswan pki --issue --lifetime 730 --cacert ipsec.d/cacerts/strongswanCert.der --cakey ipsec.d/private/strongswanKey.der \
 --dn "C=JP, O=soymsk, CN=vpnuser@soymsk.example.jp" --san vpnuser@soymsk.example.jp" \
 --outform der > ipsec.d/certs/vpnClientCert.der 

クライント秘密鍵、公開鍵、ルート証明をPEM形式に変換したのち、パスワードつきでPKCS#12 ファイルにまとめる。

# openssl rsa -inform DER -in ipsec.d/private/vpnClientKey.der -out ipsec.d/private/vpnClientKey.pem -outform PEM
writing RSA key

# openssl x509 -inform DER -in ipsec.d/certs/vpnClientCert.der -out ipsec.d/certs/vpnClientCert.pem -outform PEM

# openssl x509 -inform DER -in ipsec.d/cacerts/strongswanCert.der -out ipsec.d/cacerts/strongswanCert.pem -outform PEM


# openssl pkcs12 -export  -inkey ipsec.d/private/vpnClientKey.pem -in ipsec.d/certs/vpnClientCert.pem \
 -name "my private VPN Certificate"  -certfile ipsec.d/cacerts/strongswanCert.pem \
 -caname "strongSwan Root CA" -out myVpnClient.p12 
Enter Export Password: passwordを入力
Verifying - Enter Export Password: もう一度パスワードを入力

作りたいクライアント数分、上記のファイルを作成する。 クライアントには、

を登録して、パスフレーズを入力すればOK。

認証鍵の生成はこれで完了。

IPsecの設定

続いて、IPsecの設定を行う。あと少し!

/etc/strongswan/ipsec.conf を次のように書く。

# ipsec.conf - strongSwan IPsec configuration file

# basic configuration

config setup
      charondebug="ike 2, knl 2, cfg 2, net 2, esp 2, dmn 2,  mgr 2"


# Add connections here.
conn %default
      keyexchange=ikev2
      ike=aes128-sha256-ecp256,aes256-sha384-ecp384,aes128-sha256-modp2048,aes128-sha1-modp2048,aes256-sha384-modp4096,aes256-sha256-modp4096,aes256-sha1-modp4096,aes128-sha256-modp1536,aes128-sha1-modp1536,aes256-sha384-modp2048,aes256-sha256-modp2048,aes256-sha1-modp2048,aes128-sha256-modp1024,aes128-sha1-modp1024,aes256-sha384-modp1536,aes256-sha256-modp1536,aes256-sha1-modp1536,aes256-sha384-modp1024,aes256-sha256-modp1024,aes256-sha1-modp1024!
      esp=aes128gcm16-ecp256,aes256gcm16-ecp384,aes128-sha256-ecp256,aes256-sha384-ecp384,aes128-sha256-modp2048,aes128-sha1-modp2048,aes256-sha384-modp4096,aes256-sha256-modp4096,aes256-sha1-modp4096,aes128-sha256-modp1536,aes128-sha1-modp1536,aes256-sha384-modp2048,aes256-sha256-modp2048,aes256-sha1-modp2048,aes128-sha256-modp1024,aes128-sha1-modp1024,aes256-sha384-modp1536,aes256-sha256-modp1536,aes256-sha1-modp1536,aes256-sha384-modp1024,aes256-sha256-modp1024,aes256-sha1-modp1024,aes128gcm16,aes256gcm16,aes128-sha256,aes128-sha1,aes256-sha384,aes256-sha256,aes256-sha1!
      dpdaction=clear
      dpddelay=300s
      rekey=no
      left=%any
      leftsubnet=0.0.0.0/0
      # VPN Server証明書
      leftcert=vpnHostCert.der
      leftsendcert=always
      right=%any
      rightdns=8.8.8.8,8.8.4.4
      # rightsourceip: VPNクライアントが接続してきた際に、接続先ネットワークで割り振られるIPを指定する
      # この設定では、VPNクライアントには192.168.1.1などが割り振られることになる。
      # VPNサーバーが所属するネットワークとは別に設定すること。
      rightsourceip=192.168.1.0/24
 


conn IPSec-IKEv2
    keyexchange=ikev2
    auto=add

# OSX, iOSクライアントは主にこちら
conn IPSec-IKEv2-EAP
    also="IPSec-IKEv2"
    rightauth=eap-mschapv2
    eap_identity=%any
    # OSX, iOSではleftid(VPN Server CN)
    leftid=@soymsk.example.jp

iOS8対応などは不要のため、元記事からいくつか設定を省いている。

VPNクライアント設定

クライアントの情報を設定するには、以下のファイルを編集する。

/etc/strongswan/ipsec.secrets:

# 公開鍵認証
: RSA vpnHostKey.der

# ユーザー名・パスワード認証
# `[<domain>\]<ユーザー名> : EAP "<パスワード>" `
vpnuser : EAP "EAPパスワード"

(パスワードがハッシュ化後のものでなく、平文なのがちょっと怖い)

ファイヤウォール、ルーター設定

インターネット側からNAT(ルーター)越しにアクセスを受け付けるために、FWとルーターの設定をする。

Firewalld 設定

# firewall-cmd --add-masquerade --permanent
# firewall-cmd  --query-masquerade --permanent
# firewall-cmd --add-port=4500/udp --permanent
# firewall-cmd --add-port=500/udp --permanent
# firewall-cmd --add-service="ipsec" --permanent
# systemctl restart firewalld
# firewall-cmd --list-all # 設定を確認

ルーター設定

インターネット側からのUDPの500, 4500ポートアクセスをVPNサーバーにポートフォワードするように設定する。

sysctlの設定

VPNサーバーからローカルネットワーク内のホストへIPフォワードを有効にするために、sysctlの設定を変更する. (XX-の部分は優先度を決めるので、適切な数字で!) /etc/sysctl.d/XX-vpn.conf :

net.ipv4.ip_forward = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0

設定を反映

# sysctl -p

VPN サーバー起動

ようやくVPNサーバーの構築が終ったので起動してみる。

# systemctl enable strongswan
# systemctl start strongswan

# systemctl status strongswan
   strongswan.service - strongSwan IPsec IKEv1/IKEv2 daemon using ipsec.conf
   Loaded: loaded (/usr/lib/systemd/system/strongswan.service; enabled; vendor preset: disabled)
   Active: active (running) since Sat 2016-10-08 10:11:43 JST; 15s ago

どうやら起動に成功しているようだ。

クライアントからの接続設定

ちょっと長くなったので別記事にまとめる予定。

追記(2016/11/28)

これだけの設定では特定サイトにアクセスできない。 具体的にはMTU値を調整する必要があるが、詳しくはPart2にて。

soymsk.hatenablog.com

PPTP vs L2TP/IPSec vs OpenVPN vs IKEv2

iOS10からPPTPクライアントが廃止されてしまい、 家のバッファロールータで動いていたPPTPサーバにアクセスできなくなってしまったので、この際きちんとVPNサーバーを立ててみる。

まずはVPNの方式について調査。

VPN方式の違い

PPTP

古くからある方式だが、脆弱性があるため現在は基本的に使用しない。

  • メリット
    • 歴史が古く、多くのクライアントがある。
    • 構築がかんたん
    • 高速
  • デメリット
    • 安全ではない(が、今でも広く使われている)

L2TP or L2TP/IPsec

現在一般的な方式。L2TP単体ではなくIPsecと組み合わせて構築する。

  • メリット
    • 安全(と思われる)
    • 構築がかんたん
    • 対応クライアントが多い
    • OpenVPNより高速
  • デメリット
    • クラック済みといううわさがある(詳しくは参考記事を参照)
    • UDP:500を使用するためポートフォワーディングが必要

OpenVPN

SSLの仕組みに乗っかる形のVPN. 

  • メリット
    • 柔軟な設定
    • 高い安全性(ここで比較しているどの方式と比較しても安全)
    • SSLポートを使用しているのでファイヤウォールに穴をあける必要がない
    • 暗号化アルゴリズムを選択できる
    • オープンソース実装がベース
  • デメリット
    • 標準で対応しているクライアントが少ない
    • 設定難
    • バイルデバイス(iOS, Android)対応がいまいち

SSTP

Microsoftが開発した比較的新しい方式。

  • メリット

    • 高い安全性
    • Windowsとの親和性が高い
    • ほとんどのファイヤウォールを通過できる
  • デメリット

IKEv2

MicrosoftCiscoが開発した方式

  • メリット
    • 一時的な接続断があっても再接続を行うなど、安定性が高い。モバイル向き(←これ、ポイント高い)
    • PPTP, SSTP, L2TPより高速
    • 高い安全性
    • 構築がかんたん
  • デメリット
    • クライアントが限られる(とはいえ、最近のiOS, macOSでは対応済み)
    • 実装が難しく、開発が難しい(潜在的なバグのリスク?)

まとめ

参考サイトにある通り、OpenVPN > iKEv2 > L2TP/IPsec >> SSTP >>>>> PPTP(論外) という感じかな。

  • サードパーティ製のクライアントが必要な点を除けば、OpenVPNが最良
  • 不安定な接続環境下であればiKEv2
  • それ以外はやや安全性が劣るもののL2TP/IPsec

クライアントは主にiOS, macOSだし、外出先からつなぎたいケースがほとんどだから、IKEv2で作ってみよう。

参考

非常にわかりやすい解説だった。

Docker on vagrant on Windows10

Docker on vagrant

Dockerをvagrant上に構築してみる。 ネット上の情報を参考にしてみてもうまくいかなかったので、だいぶ試行錯誤した

目指す構成は以下のとおり

Docker Container (CentOS6)
on
Vagrant (CentOS6)
on
Windows 10

※今回はHadoopクラスタ構築を目標にしているので、Hadoopという文字列がちらほら出てきますが、そこは任意です

Why docker on vagrant

ネット上の記事を見ると、vagrantで仮想環境を立てて、その上にさらにDockerによるコンテナを構築する例が多いけど、 docker(仮想) on vagrant (仮想)と、2重に仮想環境を構築する意味が最初わからなかった。

自分で調べてみたところでは、

  • DockerはLXCを利用しており、Linux上でしか動作しない
  • そのため、OSXWindows上では、VagrantでまずLinux環境を構築してから、Dockerを入れる。
    • その際のLinux環境は最小限のもので構わないので、boot2dockerなどの軽量Linuxを用いる
  • 逆にホストOSが最初からLinuxであれば、Dockerコンテナを直に立てればいい。

利点として、どのOSで作業をしようが、どこでもDockerコンテナを立てることができるので、 別OSに環境を移植する際もDockerコンテナを持っていけばよいということかな。

Vagrantごと持って行くと、Linux上ではLinux - Vagrant - Dockerとなり、 コンテナ型仮想化であるDockerを直接利用するよりオーバーヘッドが大きい)

Docker provision vs Docker provider

VagrantでDockerを扱うには、provisionとproviderが2つあって、どちらを使えばいいのかよくわからなかったので整理。

VagrantでDockerを立てる方法は主に2つある。

Provision:

  • Vagrantが立てるVM上に、Dockerを構築する。
    あくまでVMを立ててからDockerをインストールする形になるので、ホストOSがLinuxである場合にもLinux - VM - Dockerという構成になる。
    Vagrant v1.6まではこの構成?

Provider:

  • Vagrant ver1.6から追加された機能。
    VagrantVMを構築する際に、VirtualBox等によるVMでなく、Dockerによるコンテナを指定できるというもの。
    MacWindowsなど、直接Dockerが構築できないホストOS上では、Vagrantが自動で中間のLinux VMを立ててくれる。
    一方で、ホストOSがLinuxの場合は直接Dockerコンテナを立てるため、無駄がないのかな(未確認)。

どのOSでも使え、設定が簡単そうなProviderがよさそう。

Docker on Vagrant on Window10

Windows10にVagrant を立てて、その上にDockerコンテナを構築する

準備

以下をインスト-ル(詳細は割愛)

Vagrantfile

mkdir {任意のディレクトリ}/hadoop # 以下、D:/virtualbox_vms/hadoopとする
cd D:/virtualbox_vms/hadoop
mkdir host

hadoop/Vagrantfile にDockerコンテナ自体の構築手順を、   hadoop/host/Vagrantfile にDockerをインストールするVMのVagrantfileを置く。

ここらへんは、ドキュメントも豊富なのでそちらを参考に。。(そのうち追記するかも)

Vagrantfile

VAGRANTFILE_API_VERSION = "2"
ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker'

Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
  config.vm.define "hadoop-container" do |h|
    h.vm.provider "docker" do |docker|
      docker.vagrant_vagrantfile = "./host/Vagrantfile"
      docker.name = 'hadoop-container'
      docker.build_dir = '.'
    end
  end
end

Dockerfile

FROM centos:centos6

RUN yum update -y

### 以下、Dockerコンテナの構築コマンド(省略

host/Vagrantfile

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure(2) do |config|

  config.vm.hostname = "docker-host"

  config.vm.box = "bento/centos-6.7"

  config.vm.box_check_update = false

  # provisoned by docker
  config.vm.provision "docker"
end

以上のファイルを準備したら、hadoopディレクトリ以下で、

vagrant up

と実行すれば、host/以下のVMも含めてdocker コンテナの立ち上げまでやってくれる。 はずだった。。

Permisson denied

DockerでのProvider実行時にPermisson Deniedになった。

D:\virtualbox_vms\hadoop>vagrant up
Bringing machine 'hadoop-container' up with 'docker' provider...
==> hadoop-container: Docker host is required. One will be created if necessary...
    hadoop-container: Docker host VM is already ready.
==> hadoop-container: Building the container from a Dockerfile...
    hadoop-container: stat /var/lib/docker/docker_build_7afac34b3a4f4be525c3d67a8eef89c3:
permission denied
A Docker command executed by Vagrant didn't complete successfully!
The command run along with the output from the command is shown
below.

Command: "docker" "build" "/var/lib/docker/docker_build_7afac34b3a4f4be525c3d67a8eef89c3"

Stderr: stat /var/lib/docker/docker_build_7afac34b3a4f4be525c3d67a8eef89c3: permission den
ied

Stdout:

同様の事例がissueに報告されている。 https://github.com/mitchellh/vagrant/issues/6822

これは、/var/lib/dockerがvagrant構築時にはroot権限になっているが、dockerコマンドを実行するのはvagrantユーザーであるから。

以下の行をhost/Vagrantfileに追記し、/var/lib/dockerディレクトリをvagrantユーザーのものにしておく。

# provisoned by docker
config.vm.provision "docker"
## 追記
config.vm.provision "shell", inline: <<-SHELL
  sudo chown vagrant:vagrant /var/lib/docker
SHELL

privisonは複数指定できるみたい。

これでとりあえずDockerコンテナが立ち上がるところまで確認できた。