- 投稿日:2019-03-18T23:16:33+09:00
LinuxPCで使うiscsiのパフォーマンスはどれくらい?
この記事のまとめ
linux-pcで使う「iSCSI」ってどれぐらい早い?*
⇒ 適当な 第3世代 Corei7 PC で 71.5 MB/s(572Mbps)でました。
ローカルディスクの速度と比べて
書き込み速度は55%程度
読み込み速度は83%程度。リソースどれぐらい消費するの?
⇒ Initiator 側のCPU使用率は、ローカルDisk使用時と大して変わりませんでした。
⇒ target 側のCPU使用率は 書き込み時に50%くらいまで上がりました。
⇒ ネットワーク帯域は iSCSIの転送速度に対して 105% 程度でした。実験構成
Debian 9.6 がインストールされたPCをGigabit-Ethernetでつないだ単純な構成。
スイッチも4千円以下で買えるBUFFALO の Gigabit Switching-HUBを使用。結果サマリ
ローカル iSCSI 4GB Sequential Write 127 MB/s 71.5 MB/s 4GB Sequential Read 136 MB/s 113 MB/s 書き込み速度は55%程度 読み込み速度は83%程度 を記録しました。
iSCSI書き込み時の Initiator / target の CPU使用率 , Ethernetの利用帯域
- Initiator のCPU使用率はローカル書き込み時に比べてさほど変化がありませんでした。むしろiowait timeが減ることで、見かけ上のCPU使用率はiSCSIを使用していたほうが低いです。
- 今回のtarget側PCが非力なCPUだったせいもあり、target側PCのCPU使用率は50%程に達しています。softirq,system Timeが支配的です。
- ネットワークトラフィックは 600 Mbps 程度に達しています。実際のiSCSIの書き込み速度71.5 MB/s(572Mbps)に対して 105% 程度の割合です。
![]()
ローカル書き込み時の Initiator / target の CPU使用率(比較用)
スペック詳細
iSCSI Initiator iSCSI Target kernel 4.9.0-8-amd64 4.9.0-8-amd64 CPU Intel Core i7-3770K 3.50GHz Intel Celeron 450 2.20GHz MEM 8GB 2GB HDD ST6000DM003 5400rpm ST6000DM003 5400rpm NIC 1000Base-T 1000Base-T +chip RTL8168evl/8111evl RTL8168c/8111c +Driver r8169 2.3LK-NAPI r8169 2.3LK-NAPI ethtool -k で NICのオフロード機能の状態を調べてみたところ、どちらも以下のみONでした。
- rx-checksumming: on
- generic-receive-offload: on
- rx-vlan-offload: on
- tx-vlan-offload: on
- highdma: on [fixed]
測定方法
# 4GB Sequential Write # dd で 4GBのファイルを書き込み。oflag=directで書き込みキャッシュを利用しない。 chinachu@debian:~$ dd if=/dev/zero of=/mnt/tmp.data bs=4M count=1024 oflag=direct 1024+0 records in 1024+0 records out 4294967296 bytes (4.3 GB, 4.0 GiB) copied, 33.8208 s, 127 MB/s # 4GB Sequential Read #先ほど作ったファイルをそのまま読み込む。 chinachu@debian:~$ dd if=/mnt2/tmp.data of=/dev/null bs=4M count=1024 1024+0 records in 1024+0 records out 4294967296 bytes (4.3 GB, 4.0 GiB) copied, 38.1206 s, 113 MB/s
- 投稿日:2019-03-18T22:40:51+09:00
PL-PS data transfer using AXI-DMA on Zybo-Z7-20 (Xilinx tool set 2018.3, Linux)
Requirement
- Digilent Zybo-Z7-20 board
- Xilinx tool set 2018 (Vivado, XSDK)
- Linux on Zybo-Z7-20 (See previous articles (a) and (b))
Create AXI-DMA core in Vivado project
First, We need to create a Vivado project for Zybo-Z7-20 with axi-dma core.
The detailed process is explained here in the good article, "Using the AXI DMA in Vivado" with step by step screenshots. Because we execute DMA operation from Linux application, we only need to follow the article to the end of the section "Export the hardware design to SDK".
After exporting hardware, according to my previous articles, we need to create device tree sources using XSDK. Thedts
files XSDK automatically created are to be revised a bit.Modify Device Tree Source
Basically, we need to modify
dts
files required to boot Linux as in previous articles. Here we see the additional modifications required to setup AXI-DMA.
Insystem-top.dts
, we will see the new#include
description comparing to that in the previous article. The#include
should be,system_top.dts/include/ "pl.dtsi"In "pl.dtsi", we will find the node for
axi_dma_0
which specify the parameters passed to the driver. But the Xilinx driver for axi_dma does not have interfaces for user application. That's why we need to install xilinx_axidma driver as the interface driver. As in the README, 'xilinx_axidma' requires following node underamba_pl
node.pl.dtsiaxidma_chrdev: axidma_chrdev@0 { compatible = "xlnx,axidma-chrdev"; dmas = <&axi_dma_0 0 &axi_dma_0 1>; dma-names = "tx_channel", "rx_channel"; };The parameter
dmas
is specifying the pair of the dma-channel with their IDs. Though these IDs are the values ofxlnx,device-id
in bothdma-channel
nodes, auto-generator sets the two values to0
.pl.dtsidma-channel@40400000 { compatible = "xlnx,axi-dma-mm2s-channel"; dma-channels = <0x1>; interrupts = <0 29 4>; xlnx,datawidth = <0x20>; xlnx,device-id = <0x0>; }; dma-channel@40400030 { compatible = "xlnx,axi-dma-s2mm-channel"; dma-channels = <0x1>; interrupts = <0 30 4>; xlnx,datawidth = <0x20>; xlnx,device-id = <0x0>; };We need to revise
xlnx,device-id
in laterdma-channel
as1
pl.dtsidma-channel@40400030 { compatible = "xlnx,axi-dma-s2mm-channel"; dma-channels = <0x1>; interrupts = <0 30 4>; xlnx,datawidth = <0x20>; xlnx,device-id = <0x1>; };Compile the device tree source, and move the created
dtb
file to the boot disk.Build Interface driver (xilinx_axidma)
Clone the repository
xilinx_axidma
,$ git clone https://github.com/bperez77/xilinx_axidmaWe need to define the values
CROSS_COMPILE
,ARCH
, andKBUILD_DIR
. Our cross compile environment, we only need to sourceenv.sh
in the previous articles. After setting up cross compile environment, we type simplymake
at the root ofxilinx_axidma
.$ cd xilinx_axidma $ makeFollowing files under
xilinx_axidma
should be moved torootfs
on Zybo-Z7-20.
- driver/axidma.ko
- library/libaxidma.so
- examples/axidma_transferDriver installation
Note that
axidma.ko
can only be used fromroot
account, the driver, can be installed simply with$ insmod axidma.koIf the axi-dma core is properly installed, following messages can be seen in
dmesg
.$ dmesg | grep "axidma" [ 25.223219] axidma: loading out-of-tree module taints kernel. [ 25.225089] axidma: axidma_dma.c: axidma_dma_init: 718: DMA: Found 1 transmit channels and 1 receive channels. [ 25.225100] axidma: axidma_dma.c: axidma_dma_init: 720: VDMA: Found 0 transmit channels and 0 receive channels.And device file
axidma
is found in/dev
.$ ls /dev/axidma -la crw------- 1 root root 245, 0 Mar 18 02:16 /dev/axidmaThe driver uses
read
,write
interfaces of char device to transfer data between user and kernel region. The detailed programming interface can easily be understood from the source code of the exampleaxidma_transfer
.
axidma_transfer
takes two arguments: one is the input file to be transferred to PL, and another is output file the data came from PL to be written. For example,$ axidma_transfer test.dtb test2.dtb AXI DMA File Transfer Info: Transmit Channel: 0 Receive Channel: 1 Input File Size: 0.01 MiB Output File Size: 0.01 MiB Writing output data to `test2.dtb`.In our design, rx and tx channels are connected via FIFO, the contents of the file
test.dtb
is transferred to the FIFO in PL. Immediately, the data is transferred back and stored as the filetest2.dtb
- 投稿日:2019-03-18T20:13:54+09:00
俺のオススメする監視ツール5選!!
監視ツール
監視ツールと一概に言っても色々種類あるのですが、今回はコマンド等で手軽に実行できるリソース監視ツールを紹介します。
一部、あまりオススメできないのもありますので、デメリット等よく読んでから利用してください。top
様々なOSにデフォルトで入っているかと思います。多分。
## 起動方法 $ top
- メリット
- どのOSにも入っている(多分)
- デメリット
- macだとtopコマンド自体がリソースを喰う。windowsは未確認です
- ↑僕のPCだと8〜15%ほどCPU消費してました
htop
top
コマンドの強いバージョン。
こちらで紹介されているように、top使うくらいならhtop使いましょう。ってくらい良い感じのツールです。## インストール方法 $ sudo yum install htop ## 起動方法 $ htop
- メリット
- 導入が簡単
- 見た目もよし
- デメリット
- 好きすぎて辛い
ctop
Dockerコンテナのリソース消費量を確認するのが楽。
稼働中のコンテナのリソース、選択した単体のコンテナの詳細リソースまでを表示してくれます。導入が楽なのもポイント高い。## インストール方法 $ sudo wget https://github.com/bcicen/ctop/releases/download/v0.6.0/ctop-0.6.0-linux-amd64 -O /usr/local/bin/ctop $ sudo chmod +x /usr/local/bin/ctop ## インストール方法(Macの場合) $ brew install ctop ## 起動方法 $ ctop
- メリット
- 手間をかけずに導入できる
- brewでmacにインストールできる ←ご指摘いただきありがとうございます!
- デメリット
Linux用のバイナリなのでMacでは動作しない。docker stats
などを使いましょうgtop
グラフィカルな感じにリソース監視等ができます。
Node.JSで動作してるようなので自分好みにカスタムできそうです。## インストール方法 $ npm install gtop -g ## 起動方法 $ gtop
- メリット
- Nodeが入ってたら手軽に導入できる
- グラフィカルな感じがいい感じ
- デメリット
- Nodeが入ってる前提なので入ってない場合導入ダルい
カスタム・自作方法はこちらを参考にしてみてください。
conky
ラズパイとかUbuntuで動かしてました。
導入方法とかは忘れたのですが、自分好みにカスタマイズできるので、興味ある方は是非。https://github.com/brndnmtthws/conky
まとめ
監視ツールって導入が大変だったり、設定が大変だったりしますが、単体のリソース監視をするのであれば上記で紹介したツールで十分ですかね。
個人的には
htop
とctop
が好きですが、gtop
もなかなか可能性を秘めているので今後、うまく活用できれば良いかなと思います。それとスタバで
htop
がカッコ良かったのも2年前の話ですね。
今風に行くならscreen
コマンドで画面分割しながら、Node.JS, blessed-contrib
を利用して自作したリソース監視ツールを起動させつつDocker
いじり倒すとか。今風がなんなのかわからないですが。おすすめ♪ リンク♪
- スタバ関連
- 投稿日:2019-03-18T17:58:48+09:00
インフラの自動テストを実現するツール「Serverspec」
インフラのテスト自動化を実現するツール「Serverspec」
1.自動テストツール
1.1. 目的
【自動テストツール】を導入する目的は、DevOpsの一環として、テスト工程の自動化も視野に入れ自動化により運用の効率化を図る目的と考えて調査・検証した結果を以下の記事を記載します。
1.2. 自動テストツールとは?
度重なるCIによって都度同じテストを人手でするのは、あまりにも非効率ですよね?
決まり切ったテストなら自動化し【効率性】を上げ生産性を向上すると共に、テストの人為的なミスを削減し【品質向上】が可能な【テスト自動化ツール】を導入するべきでしょう。以上を読んでいただけると分かると思いますが、自動化したテストに関しては人手が必要なくなるだけで、結果を確認する工程やテスト内容を考えるのは人手は必要です。
また、手動テストもなくならないことを踏まえたうえで自動テストツールを導入する必要があります。
既に自動テストツールを導入しているプロジェクトの殆どが、事の大小はあっても問題を抱えていることを認識したうえで導入の検討をして下さい。
1.3. 自動化すべきテスト項目の条件
まず、全てのテストが自動化すればいいのではありません。
自動化したことにより、テスト結果が望む結果であったとしても他要因でNGとなるケースを見落とすことになります。(例えばWebサービスで画面遷移に関わるテストを自動実行し、画面遷移自体は想定通りになったとしてもレイアウトや画像が間違って変わっていてもツールでは気付けない等)
自動化したほうが品質の良いテストを実施でき効率化するためにツールであることを認識したうえでテスト項目を選定することを考える。
また、文字列や数字などのテキストデータの判別は自動テストツールで容易にチェックできますが、画像やランダム性があるものの確認は自動テストツールでは実現は難しく、できたとしても品質担保を説得力のある証拠で提示するのが難しいため、手動でのテストとするべきです。
以上より自動化すべきテスト項目の条件を以下とします。
CIの度に実施すべきテスト。(同じテストを何度も繰り返し実行する内容)
既にテストコードがあり、そのテストコードで品質担保が可能であること。
テストの確認内容が自動化ツールで全て網羅できる範疇であること。
CIの度にテストコードを改修しなくてもよいこと。改修したとしても僅かであり影響範囲が小さいこと。
1.4. テスト自動化ツール選定
テスト自動化ツールも用途により多種多様です。そのため、プロジェクトに合ったツールを選定することから始める必要があります。
Webアプリケーションテスト自動化ツール「Selenium」
対象:Webアプリケーション
動作環境:ブラウザ拡張、クライアントサーバーモデル
特徴:ブラウザ上で動作するWebアプリのテストを自動化するツールです。キャプチャー・リプレイ機能では、実際に行ったテストを再現することも可能です。「Selenium IDE」により、プログラミング言語の知識が少ない人でも、簡単にテストをできるようにブラウザ操作を記録してテストスクリプトを作成する機能も備わっています。
対象:Webアプリケーション
動作環境:Windows、UNIX、Linux、MacOS等
特徴:継続的インテグレーション(CI)ツールとして有名ですが、Jenkinsにテストを自動的に行うプラグインを紐づけることにより、スケジューラー機能による定期的なテスト実行が可能になります。Javaアプリケーション単体テスト自動化ツール「JUnit」
対象:Javaモジュール
動作環境:Linux
特徴:Javaのテストを小さな単位でテスト可能。対象:サーバの設定値やパッケージがインストール確認、特定サービスの状態確認、指定ポートでの通信のテストを自動化できる。
動作環境:
特徴:サーバの設定、状態が想定通りになっているかチェックできる。
サーバにエージェントを導入する必要性がない。他にも
Windowsアプリケーションテスト自動化ツール「QCWing」
と試験内容に応じて色々な製品が存在します。
1.5. 自動テストツール選定
私はITインフラ開発者なので【Serverspec】を
今までのExcelで書かれたパラメータシートを実機をにらめっこして、ITインフラの単体試験としていた現場が多いと思われます。それを自動化することで他の作業に工数を割くことができればより開発を促進し信頼性の高いものを提供することが可能となります。1.6. Serverspecとは?
Serverspec(サーバスペック)とは、サーバ状態のテスト自動化フレームワークです。UNIX/LinuxサーバとWindowsサーバに対応します。
構築したサーバ環境が意図した通り構成されているか自動的に確認作業を実施できるツールです。
Serverspecは、Rubyで実装されています。
構成管理ツール(Ansibleなど)で自動築したサーバを想定通りの設定及びあるべき姿であることの確認までを繰り返しテストを行い、【常にあるべき姿を維持しているか】をチェックできます。Serverspecが導入されたサーバからテストを実施するためは、エージェントは必要なく、LinuxであればSSH接続、WindowsサーバはWinRM接続可能な環境であれば実現可能となっているため、手軽に組み込むことも可能となっている。
2. Serverspecインストール
参考にしたサイト
Serverspecでテスト自動化 - IDCF テックブログ
「Serverspec」を使ってサーバー環境を自動テストしよう | さくらのナレッジ
大規模サーバ更改でServerspecを使ってみました - Taste of Tech Topics
2.1. 検証環境
OS:CentOS 7.5
ミドルウェア:serverspec(2.41.3),rake(12.3.2),bundler(2.0.1),winrm(2.3.1)
前提ミドルウェア:ruby(2.3.8)、openssl-devel(1.0.2k-16)、readline-devel(l-6.2-10)、zlib-devel(1.2.7-18)2.2. Ruby2.3.8インストール
# yum install -y openssl-devel readline-devel zlib-devel # cd /root # git clone git://github.com/sstephenson/rbenv.git .rbenv # cd .rbenv # mkdir shims versions plugins # cd plugins/ # git clone https://github.com/sstephenson/ruby-build.git ruby-build # git clone git://github.com/sstephenson/rbenv-default-gems.git rbenv-default-gems # vi ~/.rbenv/default-gems bundler rbenv-rehash # vi ~/.bashrc export RBENV_ROOT="/root/.rbenv" export PATH="$PATH:$RBENV_ROOT/bin" eval "$(rbenv init -)" # source ~/.bashrc # cd /root # rbenv install -l #※1 # rbenv install 2.3.8 # rbenv global 2.3.8 # ruby -v※1:
rbenv install -l
でrbenv: no such command install
とメッセージが出力される場合は既に古いrubyを導入してため、以下コマンドで回避することができます。# .rbenv/plugins/ruby-build/install.sh2.2. Serverspecインストール
# gem install bundler※1 # bundle init # GemfileとGemfile.lockが作成されます。 # vi Gemfile # 以下を追記 gem "serverspec" gem "rake" gem "winrm" #windowsへのテストを実施する場合にインストール # bundle install --path ./ # bundlerによりGemfileに書かれたgemをインストールする※1:bundlerに関しては、bundler、bundle execについてを参照。
2.3. Serverspec初期設定
Serverspecの初期設定をするため
serverspec-init
コマンドを実行し、質問に応答します。# bundle exec serverspec-init Select OS type: 1) UN*X 2) Windows Select number: 1 # serverspecのクライアントのOSタイプの選択でUNIXである"1"を選択 Select a backend type: 1) SSH 2) Exec (local) Select number: 1 # serverspecのクライアントの接続方式の選択でSSHである"1"を選択 Vagrant instance y/n: n # Vagrantは、私の環境では未使用のため"n"を選択 Input target host name: web01 # クライアントのホスト名を入力 ※1 + spec/ + spec/web01/ + spec/web01/sample_spec.rb + spec/spec_helper.rb + Rakefile + .rspec※1:クライアントを入力するとサンプルのテストコードが作成されます。
2.4. 標準的な構成
テスト実行コマンド
rake
は、Rakefileを最初に読み込み、対象となるspecディレクトリ配下のテストコードファイル(*_spec.rd)を実行します。テスト対象のサーバを複数台とする場合は、標準的な構成すると以下のような構成となります。
├─ Rakefile ├─ Gemfile ├─ Gemfile.lock ├─ .rspec ├─ ruby │ └─ 2.3.8 │ └─ spec/ ├─ web01/ │ ├ base_spec.rb │ └ httpd_spec.rb ├─ web02/ │ ├ base_spec.rb │ └ httpd_spec.rb ├─ app01/ │ ├ base_spec.rb │ └ tomcat_spec.rb ├─ spec/db01/ │ ├ base_spec.rb │ └ mysql_spec.rb └─ spec_helper.rbテストを個々に実行する場合は、rakeコマンドの引数に
spec:<サーバアドレス>
を指定する。# bundle exec rake spec:web01だが、この構成だとサーバが増える度にサーバ毎のフォルダを作成し、同じ種別のサーバのテストコードファイルをコピーしなければならない。また、テスト実行も全てもしくはサーバ単位となることに留意すること。
3. role
Serverspecを運用するにあたり、標準構成では、上記理由によりサーバ台数規模が膨大になると管理が大変になるため、roleによるサーバ種別毎のテスト内容を分けたいと思います。
role構成については、Serverspec用のspec_helperとRakefileのサンプルをひとつを参考にさせていただきました。
3.1. role構成
role構成するにあたり、以下のようなディレクトリ、ファイル構成とします。
├── Gemfile ├── Gemfile.lock ├── Rakefile ├── common_spec/ # 他プロジェクトと共通するspecのサブモジュール │ ├── common/ │ ├── group/ │ ├── user/ │ ├── kernel/ │ ├── firewall/ │ ├── rpm/ │ └── zabbix-agent/ ├── ferture_spec/ # プロジェクト特有のspecのサブモジュール │ ├── group/ │ ├── user/ │ ├── kernel/ │ ├── firewall/ │ ├── rpm/ │ ├── service/ │ ├── cron/ │ ├── logrotate/ │ ├── zabbix-agent/ │ ├── httpd/ │ ├── tomcat/ │ └── mysql/ ├── host_vars/ # ホスト特有の値をテストしたい場合に使用 │ ├── dbsvr01.yml │ └── win-manage01.yml ├── audit │ └── json/ # テスト証跡 ├── hosts_production.yml # 本番環境のホストとrole一覧 ├── hosts_staging.yml # 開発環境のホストとrole一覧 └── spec/ # helper他。 └── spec_helper.rbrole構成に対応するため、ディレクトリを作っていきます。
mkdir -m 755 ./common_spec mkdir -m 755 ./common_spec/common/ mkdir -m 755 ./common_spec/group/ mkdir -m 755 ./common_spec/group/system mkdir -m 755 ./common_spec/user/ mkdir -m 755 ./common_spec/user/system mkdir -m 755 ./common_spec/kernel/ mkdir -m 755 ./common_spec/kernel/system mkdir -m 755 ./ferture_spec/user/web mkdir -m 755 ./ferture_spec/user/db mkdir -m 755 ./ferture_spec/user/ap mkdir -m 755 ./ferture_spec/kernel/ mkdir -m 755 ./ferture_spec/kernel/web mkdir -m 755 ./ferture_spec/kernel/db mkdir -m 755 ./ferture_spec/kernel/ap mkdir -m 755 ./ferture_spec/firewall/ mkdir -m 755 ./ferture_spec/firewall/web mkdir -m 755 ./ferture_spec/firewall/db mkdir -m 755 ./ferture_spec/firewall/ap mkdir -m 755 ./ferture_spec/rpm/ mkdir -m 755 ./ferture_spec/rpm/web mkdir -m 755 ./ferture_spec/rpm/db mkdir -m 755 ./ferture_spec/rpm/ap mkdir -m 755 ./ferture_spec/service/ mkdir -m 755 ./ferture_spec/service/web mkdir -m 755 ./ferture_spec/service/db mkdir -m 755 ./ferture_spec/service/ap mkdir -m 755 ./ferture_spec/cron/ mkdir -m 755 ./ferture_spec/cron/web mkdir -m 755 ./ferture_spec/cron/db mkdir -m 755 ./ferture_spec/cron/ap mkdir -m 755 ./ferture_spec/logrotate/ mkdir -m 755 ./ferture_spec/logrotate/web mkdir -m 755 ./ferture_spec/logrotate/db mkdir -m 755 ./ferture_spec/logrotate/ap mkdir -m 755 ./ferture_spec/zabbix-agent/ mkdir -m 755 ./ferture_spec/zabbix-agent/staging mkdir -m 755 ./ferture_spec/zabbix-agent/production mkdir -m 755 ./ferture_spec/httpd/ mkdir -m 755 ./ferture_spec/httpd/staging mkdir -m 755 ./ferture_spec/httpd/production mkdir -m 755 ./host_vars mkdir -m 755 ./audit mkdir -m 755 ./audit/json3.1.1 アドレス、roleファイル
まずは、環境毎(hosts_production、hosts_staging)にアドレス及びroleを記載するファイルをyamlファイルを分ける方針としています。
また、hosts配下にターゲットとなるアドレス(IPアドレス、名前解決できる環境であればホスト名)を設定し、roles配下で実行するtaskのディレクトリを設定します。
各タスクのディレクトリ配下に実施にテストを実施する内容を[*_spec.rb]に設定する。
例えばuserテスト定義ファイルの場合、[common_spec/user/system/user_spec.rb]、[ferture_spec/user/web/user_spec.rb]、[ferture_spec/user/db/user_spec.rb]、[ferture_spec/user/ap/user_spec.rb]と4つ保持しているが、テスト実行コマンドの引数でwebと指定した場合は、roleの設定で[common_spec/user/system/user_spec.rb]、[ferture_spec/user/web/user_spec.rb]だけをテストする仕組みとなっています。
また、環境毎(hosts_production、hosts_staging)に設定内容に差異が存在するようなテストをroleで吸収するため、[zabbix-agent/staging/zabbix-agent_spec.rb]、[zabbix-agent/production/zabbix-agent_spec.rb]と分けroleで指定環境のテストのみを実施する仕組みとなっています。
hosts_staging.yml
--- shared_settings: :ssh_opts: :user: operator :keys: /home/operator/.ssh/id_rsa :port: 22 web: :hosts: - web01.sdomain.com - web02.sdomain.com :roles: - common - group/system - user/system - user/web - kernel/system - kernel/web - firewall/web - rpm/web - service/web - cron/web - logrotate/web - zabbix-agent/staging - httpd/staging db: :hosts: - db01.sdomain.com :roles: - common - group/system - user/system - user/db - kernel/system - kernel/db - firewall/db - rpm/db - service/db - cron/db - logrotate/db - zabbix-agent/staging - mysql/staging app: :hosts: - ap01.sdomain.com :roles: - common - group/system - user/system - user/ap - kernel/system - kernel/ap - firewall/ap - rpm/ap - service/ap - cron/ap - logrotate/ap - zabbix-agent/staging - tomcat/staginghosts_production.yml
--- shared_settings: :ssh_opts: :user: operator :keys: /home/operator/.ssh/id_rsa :port: 22 web: :hosts: - web01.domain.com - web02.domain.com - web03.domain.com - web04.domain.com :roles: - common - group/system - user/system - user/web - kernel/system - kernel/web - firewall/web - rpm/web - service/web - cron/web - logrotate/web - zabbix-agent/production - httpd/production db: :hosts: - db01.domain.com - db02.domain.com :roles: - common - group/system - user/system - user/db - kernel/system - kernel/db - firewall/db - rpm/db - service/db - cron/db - logrotate/db - zabbix-agent/production - mysql/production app: :hosts: - ap01.domain.com - ap02.domain.com :roles: - common - group/system - user/system - user/ap - kernel/system - kernel/ap - firewall/ap - rpm/ap - service/ap - cron/ap - logrotate/ap - zabbix-agent/production - tomcat/production3.1.2. Rakefile
【標準Rakefile】
require 'rake' require 'rspec/core/rake_task' task :spec => 'spec:all' task :default => :spec namespace :spec do targets = [] Dir.glob('./spec/*').each do |dir| next unless File.directory?(dir) target = File.basename(dir) target = "_#{target}" if target == "default" targets << target end task :all => targets task :default => :all targets.each do |target| original_target = target == "_default" ? target[1..-1] : target desc "Run serverspec tests to #{original_target}" RSpec::Core::RakeTask.new(target.to_sym) do |t| ENV['TARGET_HOST'] = original_target t.pattern = "spec/#{original_target}/*_spec.rb" end end end標準Rakefileでは、引数によって対象サーバや実行内容を分ける構成となるrole対応できないためrole対応できるように書き換えます。
【role対応Rakefile】
require 'rake' require 'yaml' require 'rspec/core/rake_task' ## 環境変数 SPEC_ENV で環境名を指定。 spec_env = ENV['SPEC_ENV'] if spec_env path_candidate = File.expand_path("../hosts_#{spec_env}.yml", __FILE__) if File.exists?(path_candidate) hosts_defined = path_candidate else raise RuntimeError, "\n======\nERROR: No hosts defined for #{spec_env}.\n======" end else ## SPEC_ENV が省略されたら終了 end end ## 環境名に対応する定義ファイルを読む properties = YAML.load_file(hosts_defined) task :spec => 'spec:all' task :default => :spec namespace :spec do ## 定義ファイルから spec:大分類:ホスト名を全部作成する。spec:all 用 all_tasks = properties.each_pair.map { |key, values| ## 共通設定はホスト扱いしない。 next if key == 'shared_settings' values[:hosts].map {|host| 'spec:' + key + ':' + host } }.flatten.compact ## 全部実行するタスク (spec:all) desc "all target for #{spec_env}" task :all => all_tasks ## ホスト定義をまわす、大分類はmaster_rollって名前で扱う properties.each_pair do |master_roll, entries| ## 共通設定は大分類扱いしない(spec_helperで使う) next if master_roll == 'shared_settings' ## 大分類に割り当てられているroleを抽出する role_pattern = entries[:roles].join(',') namespace master_roll.to_sym do hosts = entries[:hosts] ## 大分類別に全ホスト実行するタスク (spec:大分類:all) desc "all target of #{master_roll} for #{role_pattern}" task :all => hosts.map {|h| 'spec:' + master_roll + ':' + h } ## 大分類別に個別ホスト実行するタスクを定義する (spec:大分類:ホスト名) hosts.each do |host| desc "Run serverspec tests to #{master_roll}: #{host} for #{role_pattern}" RSpec::Core::RakeTask.new(host.to_sym) do |t| ## どれかがこけても途中でやめない。 t.fail_on_error = false ENV['TARGET_HOST'] = host ENV['SPEC_ENV'] = spec_env ## specとcommon_specとferture_specをざっくり取って、定義ファイル上のロールに対応するspecを読み込ませる。 t.pattern = "{spec,common_spec,ferture_spec}/{#{role_pattern}}/**/*_spec.rb" t.rspec_opts = "--format json -o audit/json/#{host}.json" #jsonでログ出力 end end end end end3.1.3. helperファイル
各クライアントの接続方式やsshの環境別オプション、dockerにも対応など環境などに合わせるため、spec_helperを修正します。
require 'serverspec' require "docker" require 'net/ssh' require 'yaml' case ENV['SPEC_BACKEND'] ## 環境変数 SPEC_BACKEND がdocker|DOCKERだったらSSHじゃなくてDockerバックエンドを使う。 when "DOCKER", 'docker' set :backend, :docker set :docker_url, ENV['DOCKER_HOST'] || 'unix:///var/run/docker.sock' ## Dockerでためす場合、DOCKER_IMAGEを指定する。 set :docker_image, ENV['DOCKER_IMAGE'] set :docker_container_create_options, {'Cmd' => ['/bin/sh']} Excon.defaults[:ssl_verify_peer] = false else ## デフォルトのバックエンドはSSH set :backend, :ssh set :request_pty, true ## このへんはRakeと一緒、定義ファイルを決定 spec_env = ENV['SPEC_ENV'] if spec_env path_candidate = File.expand_path("../../hosts_#{spec_env}", __FILE__) puts path_candidate if File.exists?(path_candidate) hosts_defined = path_candidate else raise RuntimeError, "\n======\nERROR: No hosts defined for #{spec_env}.\n======" end else hosts_defined = File.expand_path("../../hosts_staging", __FILE__) end ## spec_helperでもRakefile同様にホスト定義を読み込む properties = YAML.load_file(hosts_defined) host = ENV['TARGET_HOST'] mainrole = properties.select {|k,v| v[:hosts].include?(host) if v[:hosts] }.keys.first ## ホスト固有の値を書いたファイルがあればつかう。 host_vars = YAML.load_file( File.expand_path("../../host_vars/#{host}.yml", __FILE__) ) if File.exists?(File.expand_path("../../host_vars/#{host}.yml", __FILE__)) spec_property = properties[mainrole] spec_property[:host_vars] = host_vars ||= {} ## 環境変数DEBUGがあったらset_propertyに渡される値を表示する puts spec_property.to_yaml if ENV['DEBUG'] set_property spec_property ## specの中で大分類を使うかもしれないと思ってとりあえず環境変数に突っ込んである。 ENV['SPEC_MAINROLE'] = mainrole ## 環境別SSH接続設定をマージしていく options = Net::SSH::Config.for(host).merge(properties['shared_settings'][:ssh_opts]) ### 大分類の下にもssh_optsがあったらそっちを優先で上書き options.merge!(properties[mainrole][:ssh_opts]) if properties[mainrole][:ssh_opts] options[:user] ||= 'root' options[:keys] ||= File.expand_path("#{ENV['HOME']}/.ssh/my_staging_key" ,__FILE__) set :host, options[:host_name] || host set :ssh_options, options # Disable sudo # set :disable_sudo, true RSpec.configure do |config| config.color = true config.tty = true end # Set environment variables set :env, :LANG => 'C', :LC_MESSAGES => 'C' end3.2. テスト実行コマンド
3.2.1. role対象サーバの確認
roleはRakefileの構成により環境変数(SPEC_ENV)により、本番環境(SPEC_ENV=production)と検証環境(SPEC_ENV=staging)を分けることが出来ように実装しています。また、さらにサーバ種別毎(spec:web:all)やサーバ単体(spec:web:web03.domain.com)、全て(spec:all)とそれぞれ実行できるようにしています。
環境毎のyamlが正常に設定されているか確認します。環境変数(SPEC_ENV)を指定しない場合は、defaultでhosts_staging.yamlを読み込みます。
# bundle exec rake -vT rake spec:all # all target for staging rake spec:app:all # all target of app for common,group/system,user/system,user/ap,ke... rake spec:app:ap01.sdomain.com # Run serverspec tests to app: ap01.sdomain.com for common,group/s... rake spec:db:all # all target of db for common,group/system,user/system,user/db,ker... rake spec:db:db01.sdomain.com # Run serverspec tests to db: db01.sdomain.com for common,group/sy... rake spec:web:all # all target of web for common,group/system,user/system,user/web,k... rake spec:web:web01.sdomain.com # Run serverspec tests to web: web01.sdomain.com for common,group/... rake spec:web:web02.sdomain.com # Run serverspec tests to web: web02.sdomain.com for common,group/...
SPEC_ENV=production
を指定し本番環境でのyamlが正常に設定されているか確認。# SPEC_ENV=production bundle exec rake -vT rake spec:all # all target for production rake spec:app:all # all target of app for common,group/system,user/system,user/ap,kernel/system,kernel/ap,firewall/ap,rpm/ap,service/ap,cro... rake spec:app:ap01.domain.com # Run serverspec tests to app: ap01.domain.com for common,group/system,user/system,user/ap,kernel/system,kernel/ap,firewa... rake spec:app:ap02.domain.com # Run serverspec tests to app: ap02.domain.com for common,group/system,user/system,user/ap,kernel/system,kernel/ap,firewa... rake spec:db:all # all target of db for common,group/system,user/system,user/db,kernel/system,kernel/db,firewall/db,rpm/db,service/db,cron... rake spec:db:db01.domain.com # Run serverspec tests to db: db01.domain.com for common,group/system,user/system,user/db,kernel/system,kernel/db,firewal... rake spec:db:db02.domain.com # Run serverspec tests to db: db02.domain.com for common,group/system,user/system,user/db,kernel/system,kernel/db,firewal... rake spec:web:all # all target of web for common,group/system,user/system,user/web,kernel/system,kernel/web,firewall/web,rpm/web,service/we... rake spec:web:web01.domain.com # Run serverspec tests to web: web01.domain.com for common,group/system,user/system,user/web,kernel/system,kernel/web,fir... rake spec:web:web02.domain.com # Run serverspec tests to web: web02.domain.com for common,group/system,user/system,user/web,kernel/system,kernel/web,fir... rake spec:web:web03.domain.com # Run serverspec tests to web: web03.domain.com for common,group/system,user/system,user/web,kernel/system,kernel/web,fir... rake spec:web:web04.domain.com # Run serverspec tests to web: web04.domain.com for common,group/system,user/system,user/web,kernel/system,kernel/web,fir... [root@sakamoto_test ~]#3.2.2. role対象サーバのテスト実行
# SPEC_ENV=production bundle exec rake spec:all #本番環境の全てのサーバ # SPEC_ENV=production bundle exec rake spec:db:all #本番環境のdbサーバ全て # SPEC_ENV=production bundle exec rake spec:web:web01.domain.com #本番環境のweb01.domain.comサーバのみ3.2.3 実行結果確認
# cat audit/json/#{host}.json | jq '.'4. テストスクリプト記述方法
テストスクリプトのフォーマットは以下のようになっています。
各リソースタイプの詳細は、公式HP リソースタイプを参照。
describe <リソースタイプ>(<テスト対象>) do <テスト条件> : : endインストールパッケージ
describe package('httpd') do ←「httpd」パッケージに関するテスト it { should be_installed.with_version('2.4.6-80') } ←パッケージがインストールされているか? endサービス設定及び状態
describe service('httpd') do ←「httpd」サービスに関するテスト it { should be_enabled } ←サービスが有効になっているか? it { should be_running } ←サービスが実行されているか? endポート状態
describe port(80) do ←80番ポートに関するテスト it { should be_listening } ←ポートが待ち受け状態になっているか? endファイル
describe file('/etc/httpd/conf/httpd.conf') do ←「/etc/httpd/conf/httpd.conf」ファイルに関するテスト it { should be_file } ←ファイルが存在するか? its(:content) { should match /ServerName localhost/ } ←ファイル内に「/ServerName localhost/」にマッチするテキストが存在するか? it { should be_owned_by('root') } it { should be_grouped_into('root') } it { should be_mode 755} endディレクトリ
describe file("/var/path/directory/") do it { should be_directory } # directoryかどうか it { should be_owned_by 'root'} # オーナーがrootか it { should be_grouped_into 'root'} # グループがrootか # 中でif文を書くことも出来ます if dir1 == 'conf' it { should be_mode 700} else it { should be_mode 755} end endcron エントリー
describe cron do it { should have_entry '* * * * * /usr/local/bin/foo' } endcron エントリー(特定ユーザ)
describe cron do it { should have_entry('* * * * * /usr/local/bin/foo').with_user('foo') } endgroup
describe group('foo_group') do it { should exist } it { should have_gid 100 } endUser
describe user('foo') do it { should exist } it { should belong_to_group 'foo_group' } it { should have_uid 100 } it { should have_home_directory '/home/foo' } it { should have_login_shell '/bin/bash' } it { should have_authorized_key 'ssh-rsa <SSH公開鍵> foo@bar.local' } endネットワークインターフェース設定
describe interface('eth0') do its(:speed) { should eq 1000 } it { should have_ipv4_address("192.168.10.10") } it { should have_ipv4_address("192.168.10.10/24") } endネットワーク疎通
describe host('web01.domain.com') do # ping it { should be_reachable } # tcp port 22 it { should be_reachable.with( :port => 22 ) } # set protocol explicitly it { should be_reachable.with( :port => 22, :proto => 'tcp' ) } # udp port 53 it { should be_reachable.with( :port => 53, :proto => 'udp' ) } # timeout setting (default is 5 seconds) it { should be_reachable.with( :port => 22, :proto => 'tcp', :timeout => 1 ) } endデフォルトゲートウェイ
describe default_gateway do its(:ipaddress) { should eq '192.168.1.1' } its(:interface) { should eq 'eth0' } end静的ルーティング
describe routing_table do it do should have_entry( :destination => '192.168.100.0/24', :interface => 'eth1', :gateway => '192.168.10.1', ) end endfirewalld
describe firewalld do its(:default_zone) { should contain 'public' } it { should have_port('161/udp') } it { should have_service('ssh') } it { should have_source('192.160.0.100/32') } it { should have_interface('eth0') } endカーネルパラメータ
describe 'Linux kernel parameters' do context linux_kernel_parameter('net.ipv4.tcp_syncookies') do its(:value) { should eq 1 } end context linux_kernel_parameter('kernel.shmall') do its(:value) { should be >= 4294967296 } end context linux_kernel_parameter('kernel.shmmax') do its(:value) { should be <= 68719476736 } end context linux_kernel_parameter('kernel.osrelease') do its(:value) { should eq '2.6.32-131.0.15.el6.x86_64' } end context linux_kernel_parameter('net.ipv4.tcp_wmem') do its(:value) { should match /4096\t16384\t4194304/ } end end確認コマンド(LDAP search)
ldapsearch_command = "ldapsearch -x -h 127.0.0.1 -b "認証に必要な情報" -w #{password}" describe command("#{ldapsearch_command} \"uid=idname\" |grep 'ftpUID:'|awk '{print $抜き取る場所}'") do its(:exit_status) {should match eq 0} its(:stdout) {should match 検証する文字列} end確認コマンド(Webサイトへのアクセス確認)
hostname=host_inventory['hostname'] # httpd port open check describe command("curl http://#{hostname}/wp-admin/install.php") do its(:stdout) { should contain('WordPress') } end最後に
こんなにも簡単にテストできるなんて、驚きの一言です。それも早い。
繰り返しテストを実施しなければならない状況なら尚更ですね。テストスクリプトもバージョン管理できるし、工夫すれば実行結果をCSV化することも可能なのでヘッダーを付けExcelすればテスト結果報告書の作成もあっと言う間にできちゃいます。
もちろん、全てをserverspecで自動化することは不可能でしょうが、部分的に自動化することができるだけでも工数削減はいとも簡単に実現できることは確実でしょう。
- 投稿日:2019-03-18T17:20:27+09:00
Dockerコンテナ上でJavaプログラムを動かすときにLANG環境変数を設定すると日本語のファイル名が文字化けする問題
概要
CentOS の Docker コンテナ上で Java プログラムを動かしていたところ、日本語のファイル名を含むファイル一覧の取得で謎の文字化けが発生しました。
Sample.javaimport java.io.*; public class Sample { public static void main(String[] args) { // ファイル名が日本語のファイル「/sample/あいうえお.csv」を配置しておく new File("/sample").listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { System.out.println(name); // => ファイル一覧を取得すると日本語ファイル名が文字化けする return false; } }); } }ちなみに、LANG環境変数を
en_US.UTF-8
とした場合は文字化けが発生せず、ja_JP.UTF-8
とした場合は文字化けが発生することが確認できています。本記事では日本語ファイル名の文字化けの原因と対処方法について記載します。
原因と対処方法
まず、LANG環境変数へ
ja_JP.UTF-8
を設定すると文字化けが発生する原因ですが、これは Docker の CentOS イメージに日本語ロケールが登録されていないため です。LANG環境変数に指定可能なロケールについては
locale -a
コマンドから確認することができます。
CentOS イメージのコンテナ内でコマンドを実行して確認してみます。# locale -a C POSIX en_US.utf8上記の通り、Docker の CentOS イメージのコンテナ内には日本語ロケールが含まれていません。
このコンテナ内で以下のようにLANG環境変数を指定してJavaプログラムからファイル一覧を取得しようとすると日本語ファイル名の文字化けが発生します。LANG=ja_JP.UTF-8 export LANG java Sample => 文字化けした日本語ファイル名.csv対処方法として、
localedef
コマンドを使用して 日本語ロケールを追加する ことで文字化けが解消します。
以下のコマンドを Dockerfile の RUN 命令として追加するかコンテナ内で実行します。# localedef -f UTF-8 -i ja_JP ja_JP.UTF-8もう一度
locale -a
コマンドで指定可能なロケールを確認してみます。# locale -a C POSIX en_US.utf8 ja_JP.utf8
localedef
コマンドによってja_JP.utf8
が追加されました。
これでLANG環境変数を設定した場合も文字化けすることなく日本語のファイル名を扱えるようになります。結論
- Docker の CentOS イメージのコンテナ内には日本語ロケールが含まれていない
- 日本語ロケールは localedef コマンドで追加できる
- 指定できない(環境に存在しない)ロケールをLANG環境変数から指定するとJavaプログラムで日本語ファイル名が文字化けする
- 投稿日:2019-03-18T10:37:23+09:00
[メモ]複数ログファイルを結合&ソートする
- 投稿日:2019-03-18T09:33:22+09:00
Docker Desktop の復習と、Windows Container に入門: Docker Desktop + Linux Container 復習編
以前、Windows Native な Docker Container を試した際、Image が 10 GB 近くあったため、そっ閉じしたままになっていた。
それが、風の噂で色々進んでいるよと聞いたので、もう一度しっかり入門してみる。Docker Desktop の復習と、Windows Container に入門: Docker Desktop + Linux Container 復習編
Docker Desktop の復習と、Windows Container に入門: Windows Server Container 理論編
Docker Desktop の復習と、Windows Container に入門: Windows Hyper-V Container, LCOW 理論編
Docker Desktop の復習と、Windows Container に入門: 実践編まずは、Windows と Docker との歴史をまとめながら、使い慣れた Docker Desktop + Linux Container について、復習していく。
Docker
Docker については、既に素晴らしい入門が他に存在しているので割愛する。
全容をしっかり知りたいのであれば、英語だが公式を見ると良いと思う。
https://docs.docker.com/get-started/日本語であれば、以下が最も網羅的な解説となっている。
https://employment.en-japan.com/engineerhub/entry/2019/02/05/103000Container と Windows
1. 背景
上記紹介記事にもあるが、元々 Docker は Linux の持つ cgroup, Namespace, chroot 等の機能を利用して構築 されており、他の Platform へ簡単に移植することはできなかった。
その為、Windows や Mac OS では、VirtualBox や xhyve, Hyper-V 上に Linux VM を構築し、それを Host Machine からできるだけ透過的に操作できるように工夫していた。
しかし、Microsoft は早い段階から Windows Native な Container の実現に前向だった。
2. 沿革
● 2013/3 - Docker を OSS 化
この頃 Windows ユーザは、VirtualBox 等に VM を立てて、その中で Docker を利用していた。● 2014/4 - Boot2Docker v0.2 がリリース
これにより、VM, Guest OS, Docker, MSYS base Terminal がワンパッケージで導入され、アイコンワンクリックで Docker が使えている 風 に見えるようになった。
とはいえ、 Volume や Network の統合は無く、結局現実に呼び戻される。● 2014/10 - Microsoft と Docker が協業を発表
Windows Server への Docker Engine 統合、Windows Native Client 開発、Dockerhub による Windows Container Image 管理の実現を発表した。● 2014/11 - Docker CLI for Windows がリリース
ここで初めて Windows Native で動く Docker Client が生まれた。
しかし、相変わらず Docker が動いているのは VM 上の Linux だ。● 2015/5 - Windows Server 2016 Technical Preview 2 リリース
Windows Nano Server が提供される。● 2015/8 - Windows Server 2016 Technical Preview 3 リリース
念願の Windows Server Container が提供される。● 2015/11 - Windows Server 2016 Technical Preview 4 リリース
少し遅れて Hyper-V Container が提供される。● 2016/4 - Windows Server 2016 Technical Preview 5 リリース
Windows Container Image の DockerHub での利用が可能に。
ただし、この時点での WindowsServerCore Image はディスク上で 約 9 GB, WindowsNanoServer Image でも 約 600 MB と、Linux Container 並の Portability を実現するには少し辛いサイズであった。● 2016/7 - Docker for Mac/Windows が正式リリース
OS Native Hypervisor ( Win: Hyper-V, Mac: xhyve ) を利用した Docker アプリケーション。
Docker が動くのが VM 上の Linux であることに変わりは無いが、Volume や Network 周りが見事に統合されていて、ホストマシン上で直接操作しているかのような使用感が得られる。● 2016/8 - Windows 10 Pro が Hyper-V Container に対応
Desktop OS でも Windows Container が利用できるようになった。● 2017/9~10 - Windows 10 Fall Creators Update と Windows Server 1709 で LCOW ( Linux Containers on Windows ) に対応
Windows 版 Docker Engine での Linux Container 立ち上げが可能に。● 2018/8 - Windows Container Image のサイズがどんどん小さくなっていく
この時点での WindowsServerCore Image はディスク上で 約 3.6 GB, WindowsNanoServer Image でも 約 100 MB 未満● 2018/8 - Docker for Windows/Mac の 2.0.0.0 がリリース。同時に名称を Docker Desktop for Windows/Mac に変更
● 2019/2 - Docker Desktop 2.0.0.2 で Windows 10 Pro が Windows Server Container に対応
いよいよ環境が全て整った。
3. 用語の整理
Linux Container
Linux Kernel で動作する Container のこと。
Windows Container
Windows の NT Kernel で動作する Container のこと。
場合によって呼び方は異なるが、多分公式にもこう呼ばれているはず。
Windows Server Container
Windows Container の実現方法の 1 つ。
Process レベルで分離される。Windows process container とも呼ばれる。
Hyper-V Container
Windows Container の実現方法の 1 つ。
kernel レベルで分離される。Windows Hyper-V container とも呼ばれる。
LCOW ( Linux Containers on Windows )
Windows Native Docker Engine によって Linux Container が動かせる機能。
技術的には Hyper-V Container とほぼ同じで、Hyper-V 上で小さな Linux VM を立ち上げて、そこで実行される。Docker Desktop エコシステム復習
以降では、Docker Desktop + Linux Container エコシステムについて復習していく。
従来の構成は、Hyper-V 上に設けられた完全な VM 上にある Docker Daemon に、Windows 上の Docker Client で接続して操作する。
↓ ざっくりとしたイメージ図
以下、重要な部分だけ確認していく。
Windows
まずは、Windows 側がどうなっているかを見ていく。
起動している関連サービスは、以下。PS> ps | wsl grep -i -e ProcessName -e '---' -e docker -e vpnkit # Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName # ------- ------ ----- ----- ------ -- -- ----------- # 577 11 10408 18672 11.89 27256 13 com.docker.proxy # 4385 111 184944 50004 20764 0 com.docker.service # 1042 64 119364 91780 17.83 23480 13 Docker Desktop # 35 3 484 2052 8376 0 Docker.Watchguard # 35 4 512 2068 8992 0 Docker.Watchguard # 255 16 20516 29680 7912 0 dockerd # 641 66 20412 12184 15.80 28084 13 vpnkitdockerd, vpnkit 以外は多分ソースが公開されていないと思われる。
その為、本記事の Docker Desktop, com.docker.service, com.docker.proxy, Docker.Watchguard に関する解説は、全て外面的な情報を元にした推測であるということ、くれぐれも注意されたし。● Docker Desktop
Docker エコシステム全体を統括するプロセス。
各サービスの初期化や起動/再起動/停止、設定変更やアップデートを行う。● dockerd
Linux Container Mode では dockerd, Container 含め全て LinuxKit 上にあり、Windows 側の dockerd は何もしていないと思われる。● com.docker.service
Docker 関連サービスの親サービス。
com.docker.proxy
,vpnkit
,Docker.Watchguard
等を子サービスとして持つ。
このサービス自体が何をしているかは不明。● com.docker.proxy
Docker Daemon API を LinuxKit 上へと Proxy するサービス。
詳細後述。● vpnkit
LinuxKit からの Outbound Packet の Host への転送や、Port Forwarding Packet の転送を行うサービス。
詳細後述。● Docker.Watchguard
全くの謎。Linuxkit
Container 用 OS をビルドするためのツールキット、またはそれによりビルドされた OS のこと。
https://github.com/linuxkit/linuxkitYAML 定義を元に Image がビルドされる。
Desktop Docker の場合は、インストール時 ( アップグレード時も? ) に最新 Image を取得して、Hyper-V 上に展開してくれる。接続
どんな Image なのか調査する為、Linuxkit に繋ぎたかったのだが、sshd が見つからなくて、Hyper-V Manager からの接続もできないので、裏技 を使って中に入る。
$ uname -a # Linux docker-desktop 4.9.125-linuxkit #1 SMP Fri Sep 7 08:20:28 UTC 2018 x86_64 Linux ### ビルド時に利用された定義は、以下にコピーされている $ cat /etc/linuxkit.yml # kernel: # image: linuxkit/kernel:4.9.125-4ffac525e6a57ccc3f2a8ae0fb96f12169027759-amd64 # cmdline: console=ttyS0 page_poison=1 vsyscall=emulate panic=1 # ...Build
LinuxKit のビルドは、
linuxkit.yml
のkernel
→init
→onboot
→onshutdown
→services
→files
セクションの順に実行される。全ての処理が、Container Image の展開か Container の実行で行われる。
kernel
セクションで kernel を/boot
フォルダに展開し、init
セクションで、Containerd, RunC, getty 等が導入されている。$ ctr version # Client: # Version: v1.1.2 # Revision: 468a545b9edcd5932818eb9de8e72413e616e86e # # Server: # Version: v1.1.2 # Revision: 468a545b9edcd5932818eb9de8e72413e616e86e $ runc -v # runc version 1.0.0-rc5+dev # commit: 69663f0bd4b60df09991c08812a60108003fa340 # spec: 1.0.0
onboot
セクションにある定義は、直接 runc を呼び出して実行される。
各種初期設定を行った残骸が残っている。$ runc list # ID PID STATUS BUNDLE CREATED OWNER # 000-metadata 0 stopped /containers/onboot/000-metadata 2019-02-20T21:17:29.2710123Z root # 001-sysfs 0 stopped /containers/onboot/001-sysfs 2019-02-20T21:17:30.6890069Z root # 002-binfmt 0 stopped /containers/onboot/002-binfmt 2019-02-20T21:17:31.6772885Z root # 003-sysctl 0 stopped /containers/onboot/003-sysctl 2019-02-20T21:17:32.0187455Z root # 004-format 0 stopped /containers/onboot/004-format 2019-02-20T21:17:32.5950764Z root # 005-extend 0 stopped /containers/onboot/005-extend 2019-02-20T21:17:33.834064Z root # 006-mount 0 stopped /containers/onboot/006-mount 2019-02-20T21:17:41.5659989Z root # 007-swap 0 stopped /containers/onboot/007-swap 2019-02-20T21:17:43.3930679Z root # 008-move-logs 0 stopped /containers/onboot/008-move-logs 2019-02-20T21:17:50.9157579Z root # 009-mount-docker 0 stopped /containers/onboot/009-mount-docker 2019-02-20T21:17:51.5884119Z root # 010-mount-kube-images 0 stopped /containers/onboot/010-mount-kube-images 2019-02-20T21:17:52.2584598Z root # 011-bridge 0 stopped /containers/onboot/011-bridge 2019-02-20T21:17:52.5884599Z root # 012-vpnkit-9pmount-vsock 0 stopped /containers/onboot/012-vpnkit-9pmount-vsock 2019-02-20T21:17:52.9334929Z root # 013-rngd1 0 stopped /containers/onboot/013-rngd1 2019-02-20T21:17:53.5926046Z root # 014-windowsnet 0 stopped /containers/onboot/014-windowsnet 2019-02-20T21:17:53.963468Z rootLinuxkit は、基本的に読み込み専用なので、全てのサービスを Container として立ち上げている。
services
セクションにある定義は、containerd によりservices.linuxkit
Namespace で実行される。$ ctr namespace ls # NAME LABELS # services.linuxkit $ ctr -n services.linuxkit container ls # CONTAINER IMAGE RUNTIME # acpid - io.containerd.runtime.v1.linux # diagnose - io.containerd.runtime.v1.linux # docker - io.containerd.runtime.v1.linux # kmsg - io.containerd.runtime.v1.linux # rngd - io.containerd.runtime.v1.linux # socks - io.containerd.runtime.v1.linux # trim-after-delete - io.containerd.runtime.v1.linux # vpnkit-forwarder - io.containerd.runtime.v1.linux # vpnkit-tap-vsockd - io.containerd.runtime.v1.linux # vsudd - io.containerd.runtime.v1.linux # write-and-rotate-logs - io.containerd.runtime.v1.linux最終的には、こんな Process Tree となる。
$ pstree # init-+-containerd-+-containerd-shim---acpid # | |-containerd-shim---diagnosticsd # | |-containerd-shim-+-docker-init---entrypoint.sh-+-logwrite---kubelet # | | | |-logwrite---lifecycle-serve---transfused.sh # | | | `-start-docker.sh---dockerd-+-containerd-+-7*[containerd-shim---pause] # | | | | |-containerd-shim---etcd # | | | | |-containerd-shim---kube-apiserver # | | | | |-containerd-shim---kube-controller # | | | | |-containerd-shim---kube-scheduler # | | | | |-containerd-shim---kube-proxy # | | | | |-2*[containerd-shim---coredns] # | | | | |-containerd-shim---nsenter---sh---pstree # | | | | `-containerd-shim---nginx---nginx # | | | `-vpnkit-expose-p # | | |-rpc.statd # | | `-rpcbind # | |-containerd-shim---kmsg # | |-containerd-shim---rngd # | |-containerd-shim # | |-containerd-shim---trim-after-dele # | |-containerd-shim---vpnkit-forwarde # | |-containerd-shim---vpnkit-tap-vsoc---vpnkit-tap-vsoc # | |-containerd-shim---vsudd # | `-containerd-shim---logwrite # |-memlogd # `-rungetty.sh---login---shdockerd
肝心の
dockerd
は、services
セクションで起動されたdocker-init
Container 上で起動されている。
自身から fork した形で Container Process をぶら下げているので、docker.sock
を mount しない方の dind っぽくなっている。$ ctr --namespace services.linuxkit tasks exec --exec-id 1000 docker docker version # Client: Docker Engine - Community # Version: 18.09.2 # API version: 1.39 # Go version: go1.10.8 # Git commit: 6247962 # Built: Sun Feb 10 00:11:44 2019 # OS/Arch: linux/amd64 # Experimental: false # # Server: Docker Engine - Community # Engine: # Version: 18.09.2 # API version: 1.39 (minimum version 1.12) # Go version: go1.10.6 # Git commit: 6247962 # Built: Sun Feb 10 00:13:06 2019 # OS/Arch: linux/amd64 # Experimental: truePersistence Data
永続化が必要なデータは、
/var/lib
以下にまとめられている。
/var/lib
には、/dev/sda1
が mount されている。$ mount -l | grep /var/lib # /dev/sda1 on /var/lib type ext4 (rw,relatime,data=ordered) # ... $ ls -l /var/lib # total 1048636 # drwxr-xr-x 5 root root 4096 Feb 18 05:39 cni # drwx------ 9 root root 4096 Feb 18 05:22 containerd # drwx--x--x 15 root root 4096 Feb 25 02:51 docker # drwxr-xr-x 3 root root 4096 Feb 20 08:58 dockershim # drwxr-xr-x 3 root root 4096 Feb 22 02:40 etcd # drwxr-xr-x 3 root root 4096 Feb 20 08:59 kubeadm # drwx------ 9 root root 4096 Feb 20 08:58 kubelet # drwxr-xr-x 3 root root 4096 Feb 18 05:38 kubelet-plugins # drwxr-xr-x 4 root root 4096 Feb 22 04:55 log # drwx------ 2 root root 16384 Feb 18 05:22 lost+found # drwxr-xr-x 3 root root 4096 Feb 18 05:22 nfs # -rw------- 1 root root 1073741824 Feb 25 02:50 swapVolume Sharing
Docker for Windows で Shared Driver に設定されたドライブは自動で共有フォルダとなる。
↓ File 共有に出された Drive は、Linux 側で
/host_mnt/*
というパスに変換されて mount される。
( 多分 Docker Client が勝手に Path 変換をしているんだろうと予想 )
その実態は、
services.linuxkit/docker
コンテナ内の/host_mnt/*
に CIFS で mount される。$ ctr --namespace services.linuxkit tasks exec --exec-id 1000 docker mount -l | grep host_mnt # //10.0.75.1/C on /host_mnt/c type cifs (rw,relatime,vers=3.02,sec=ntlmsspi,cache=strict,username=<<Windows User>>,domain=<<Windows PC Name>>,uid=0,noforceuid,gid=0,noforcegid,addr=10.0.75.1,file_mode=0755,dir_mode=0777,iocharset=utf8,nounix,serverino,mapposix,nobrl,mfsymlinks,noperm,rsize=1048576,wsize=1048576,echo_interval=60,actimeo=1)Network
dockerd Container は LinuxKit Host の Default Network Namespace と同じ Namespace が割り当てられているので、以降は LinuxKit Host のネットワーク環境として見ていく。
現在、おおよそ以下の NIC が存在している。
◆ Host Namespace
NIC Name IP master Default
Routelo 127.0.0.1/8 eth0 192.168.65.3/28 ○ hvint0 10.0.75.2/24 docker0 172.17.0.1/16 vethXXXXXXXXX@ifXXX docker0 cni0 10.1.0.1/16 vethXXXXXXXXX@eth0 cni0 tunl0@NONE ip6tnl0@NONE ◆ Container Namespace
NIC Name IP master lo 127.0.0.1/8 eth0@ifXXX 172.17.X.X/16 tunl0@NONE ip6tnl0@NONE Interface: eth0
一見、一番簡単そうに見えて一番難しい NIC。
Linuxkit の Default Network Namespace の Default Route デバイス。
192.168.65.0/28
には、Default Gateway である192.168.65.1
と、Windows Host を示す192.168.65.2
がある。$ ip n show dev eth0 # 192.168.65.1 lladdr f6:16:36:bc:f9:c6 ref 1 used 0/0/0 probes 1 REACHABLE # 192.168.65.2 lladdr f6:16:36:bc:f9:c6 used 0/0/0 probes 4 STALE一見すると物理 NIC にも見えるが、実は TAP 仮想デバイスであり、その裏では vpnkit というツールが Hyper-V Socket, vsock を利用して通信のトンネリング・仲介をしている。詳細な原理は後述。
Interface: hvint0
Docker Desktop は導入時、
DockerNAT
という仮想 Switch を作る。
LinuxKit はそのDockerNAT
に接続された状態で起動される。Windows 側には
イーサネット アダプター vEthernet (DockerNAT)
という仮想 NIC が作成され、LinuxKit 側にはhvint0
という NIC が作られ ( 正確には、起動時にeth0
だった物理 NIC をリネームしている )、どちらもDockerNAT
に接続される。
この経路は主にドライブの mount 用に利用されるようだ。Network: docker0, vethXXXX@ifXXX
Docker が構築するいつものネットワーク。
各 Container は、Host とは違う Network Namespace をそれぞれ持つ。
veth のペアは、一つは Host Namespace に、もう一つは各 Container Namespace に配置される。
docker0
は bridge であり、veth に master としてリンクされている。
また、docker0
は IP Address も持っており、各 Container Namespace の Default Gateway となっている。また iptables の IPマスカレード機能により、
docker0
を通る Container の Outbound Packet 全て送信元 IP 変換がなされる。Network: cni0, vethXXXX@eth0
CNI プラグインで利用されるネットワーク。Kubernetes が有効になっていると作成される。
CNI ( Container Network Interface ) とは、Container の Networking を担当するプラグインの I/F 仕様。
多くの Container Runtime や Orchestrator が登場する中、各社独自の Networking 実装による重複を避ける目的がある。各 Pod は、Host とは違う Network Namespace をそれぞれ持つ ( Pod 内の Container は同じ Network Namespace )。
今回は具体的な CNI プラグイン実装が入っていないが、例えば Flannel 等でクラスタが構築されれば多分以下のようになるはず。今回は Kubernetes は射程外なので ( というか、自分自身が詳しくもないので ) あまり踏み込まない。
Tunnel: tunl0@NONE, ip6tnl0@NONE
稀に遭遇する謎のデバイス。一体何のためにあるのか分からなかった。
ちなみに、Container の中にもいる。$ ip tunnel show # tunl0: unknown/ip remote any local any ttl inherit nopmtudisc $ ip addr show tunl0 # 3: tunl0@NONE: <NOARP> mtu 1480 qdisc noqueue state DOWN qlen 1 # link/ipip 0.0.0.0 brd 0.0.0.0 $ ip link set dev tunl0 up $ ip addr show tunl0 # 3: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN qlen 1 # link/ipip 0.0.0.0 brd 0.0.0.0 $ ping -I tunl0 172.17.0.2 # PING 172.17.0.2 (172.17.0.2): 56 data bytes # . # . # . # ( 沈黙 )calico の Github Issues にある情報だが、IPIP カーネルモジュールが読み込まれたときの副作用で作られるとの情報あり。
- tunl0 device present inside containers - github.com/projectcalico/calicoctl
- tun10@NONE interface inside the pods is of no use - github.com/projectcalico/calico
- what does none in “tun0@none” stand for? - Unix & Linux Stack Exchange
Docker 公式にもしれっと居たりする。そして触れられないという。
https://docs.docker.com/network/none/ Host-Guest 間 socket 通信
古くは VMWare の VMCI Socket、最近では Qemu で使われる virtio-vsock ( Address-Fammily = AF_VAOCK ) という技術を使うことで、Network を一切介さずに VM Guest と Host の間で通常の BSD socker API を使った通信が可能となる。
メモリを共有し、その上でデータ交換するので高速な通信が可能となる。https://medium.com/@mdlayher/linux-vm-sockets-in-go-ea11768e9e67
https://pubs.vmware.com/vsphere-51/index.jsp?topic=%2Fcom.vmware.vmci.pg.doc%2FvsockAbout.3.2.html
https://wiki.qemu.org/Features/VirtioVsockそして 2017 年、ついに Hyper-V にもこの Host-Guest 間 socket 通信ができる機能が追加された。
Docker Desktop では至る所でこの Hyer-V Socket が利用されている。Hyper-V Socket
Hyper-V Host と Guest との間で通信を行う Socket。2017 年頃に Windows 10, Windows Server 2016 に導入された。
Network を介さず、VMBus 経由でやり取りするのでハイパフォーマンス。
https://docs.microsoft.com/en-us/virtualization/hyper-v-on-windows/user-guide/make-integration-service● Socket Address Family
この Socket を実現するため、socket のアドレスファミリに
AF_HYPERV
が追加された。
Linux Guest 側は vsock を利用する。● Guest Communication Service
Hyper-V Socket を利用するには、まずは Windows に Guest Communication Service というものを登録する必要がある。
これは、Unix Domain Socket で言うところの File Path のような、通信チャンネルの識別子的なもので、Windows Host の Registry に登録される。
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Virtualization\GuestCommunicationServices
にある。見てみると、既に Docker や Kubernetes 関連のサービスがいくつか登録されているのが分かる。
● Service GUID 命名規則
Hyper-V Socket と vsock では通信先アドレスの指定方法が違っていて、Hyper-V Socket の場合は
VM GUID
とService GUID
を指定するが、vsock の場合はcid
とport
( 0 ~ 0x7FFFFFFF の数値 ) を指定する。これらを両立させる為に、Service ID としての GUID を決める際には以下のルールに則る。
[[ Port Number ]]-FACB-11E6-BD58-64006A7986D3例えば、Service ID
00000948-FACB-11E6-BD58-64006A7986D3 ( ElementName : Docker API )
について通信したい場合、以下の様な設定になる。
- Hyper-V Socket
- VM GUID
(Get-VM -Name 'DockerDesktopVM').Id
- Service GUID
00000948-FACB-11E6-BD58-64006A7986D3
- vsock
- cid
VMADDR_CID_HOST
( これ一択 ? )- port
- 0x00000948 →
2376
また、Docker Desktop エコシステム中で利用される場合には、
[[Protocol]]://[[VM ID]]/[[SERVICE ID]]
のような Path 表記もされる。# 30D48B34-FACB-... サービスについて、全ての VM からの接続要求を待つ hyperv-listen://00000000-0000-0000-0000-000000000000/30D48B34-FACB-11E6-BD58-64006A7986D3 # 0000F3A5-FACB-... サービスについて、全ての VM からの接続要求を待つ hyperv-listen://00000000-0000-0000-0000-000000000000/0000F3A5-FACB-11E6-BD58-64006A7986D3 # 0000F3A5-FACB-... サービスについて、VM (GUIT: ABCDEFGH-IJKL-...) に接続する hyperv-connect://ABCDEFGH-IJKL-MNOP-QRST-UVWXYZZZZZZZ/0000F3A5-FACB-11E6-BD58-64006A7986D3vsudd & com.docker.proxy.exe
Docker API 通信を Hyper-V socket で Tunneling して
docker.sock
へと Proxy するサービス。
これにより、Windows Host からdockerd
が操作できる。
https://github.com/linuxkit/virtsock/tree/master/cmd/vsudd
LinuxKit 上で起動した
vsudd
は、vsock(cid=VMADDR_CID_ANY, Port=00000948)
で待ち受けて、受け取ったデータをdocker.sock
Unix Domain Socket へと Proxy する。Windows Host 側では、サービスにより起動された
com.docker.proxy.exe
が Named Pipe//./pipe/docker_engine
で待ち受けて、受け取ったリクエストをhyperv-listen://XXXXXXXX-XXXX-.../00000948-FACB-...
宛に転送する。Docker Client から dockerd 宛に指示を出す時は、
docker -H npipe://./pipe/docker_engine ~
となる。VPNKit
Hyper-V socket/vsock を利用して様々な通信の仲介・Tunneling をするための Toolkit。 OCaml, Go, C で実装されている。
https://github.com/moby/vpnkit/以下、主要なサービス。
- On Linux Guest
- vpnkit-tap-vsockd
- Guest Communication Service : Docker VPN proxy ( vsock port : 0x30D48B34 )
- Container, LinuxKit Host から外部ネットワークへの通信経路を提供
- TAP デバイス
eth0
を設置eth0
( vpnkit-tap-vsockd ) ⇔vpnkit.exe
を Hyper-V socket で Tunneling- vpnkit-forwarder
- Guest Communication Service : Docker port forwarding ( vsock port : 0x0000F3A5 )
- Windows Host から Linxkit Host への Port Forwarding 機能を提供
vpnkit.exe
⇔vpnkit-forwarder
を Hyper-V socket で Tunnelingvpnkit-forwarder
⇔Container
間の Forwarding には、vpnkit-expose-port
という別の担当がいる- Port が Leak しないように、9p filesystem ベースの管理を行う
- Linuxkit 起動時に 9p filesystem を mount するのは
vpnkit-9pmount-vsock
が行う- On Windows Host
- vpnkit.exe
vpnkit-tap-vsockd
からの Frame を受け取り、Ethernet に流す
- hyperv-listen://00000000-0000-0000-0000-000000000000/30D48B34-FACB-11E6-BD58-64006A7986D3
vpnkit-expose-port
からの Port Forward 要求をうけとり、可否を返す。可ならその Port で自身が Listen。
- hyperv-listen://00000000-0000-0000-0000-000000000000/0000F3A5-FACB-11E6-BD58-64006A7986D3
- Port を Listen し、受け取った Packet を connect 先の
vpnkit-forwarder
に流す
- hyperv-connect://XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/0000F3A5-FACB-11E6-BD58-64006A7986D3
● vpnkit-tap-vsockd
LinuxKit → Windows で Ethernet over vsock/Hyper-V socket Tunneling を構築し、Container 内から Windows Host や Internet への通信を実現するサービス。
https://github.com/moby/vpnkit/blob/master/docs/ethernet.md
https://github.com/moby/vpnkit/tree/master/c/vpnkit-tap-vsockd
LinuxKit 内で外向け Packet が Default Route のeth0
に到着すると、vpnkit-tap-vsockd
はそれを読み取り、Encapsulation してvsock(cid=VMADDR_CID_HOST, Port=30D48B34)
へ向けて送信する。
vpnkit.exe
はhyperv-listen://00000000-0000-.../30D48B34-FACB-...
で待ち受けており、受け取ったデータを Decapsulation し、vpnkit.exe
プロセス内部に持っている仮想 L3 Switch へと送る。
vpnkit は、送信先毎に 仮想 TCP/IP endpoint を作成しており、これが Transport Layer ( L4 ) Proxy として TCP/UDP Flow を終端する。
内部 Switch はこの仮想 TCP/IP Endpoint に対し 1 つの Switch Port を接続しておき、送信先で判定し Filtering する。
もし知らない送信先が来た場合、新たに仮想 TCP/IP Endpoint が作られ、新しい Switch Port が作成 & 接続される。これらは全て
vpnkit.exe
プロセス内部で起こることで、Windows Host Kernel からはvpnkit.exe
が複数の相手と socket 通信しているようにしか見えない。● vpnkit-forwarder
Windows → LinuxKit で Port Forwarding を実現するサービス。
https://github.com/moby/vpnkit/blob/master/docs/ports.md
https://github.com/moby/vpnkit/tree/master/go/cmd/vpnkit-forwarder ( 元proxy-vsockd
)
https://github.com/moby/vpnkit/blob/master/go/cmd/vpnkit-userland-proxy ( 旧slirp-proxy
, 現vpnkit-expose-port
)
https://github.com/moby/vpnkit/tree/master/c/vpnkit-9pmount-vsock前準備
まずは前準備として、
vpnkit.exe
起動時に Port Forwarding 情報の共有のための 9p Server を立ち上げhyperv-listen://00000000-0000-.../0000F3A5-FACB-...
で待ち受ける。
Linuxkit 側では、onboot
時にvpnkit-9pmount-vsock
Container がvsock(cid=VMADDR_CID_HOST, Port=0000F3A5)
で接続し、その socket を Backend とした 9P filesystem を/port
に mount する。### `rfdno`, `wfdno` に設定されているのが、socket の file descriptor $ ctr --namespace services.linuxkit tasks exec --exec-id 1000 docker mount -l | grep /port # /port on /port type 9p (rw,relatime,sync,dirsync,trans=fd,dfltuid=1001,dfltgid=50,version=9p2000,msize=4096,rfdno=3,wfdno=3)Port Forwarding
それでは、実際に Port Forwarding されるまでの一連の処理を見ていく。
Container を立ち上げる。
PS> docker run -d -p 80:80 nginxDocker Client から指示を受けた
dockerd
は、指定の IP, Port に対応したvpnkit-expose-port
プロセスを Fork する。
vpnkit-expose-port
は、指定した IP:Port で Listen し、これまた指定した Container へと転送する Forward Proxy だ。$ ps | grep /usr/bin/vpnkit-expose-port # 3404 root 0:00 /usr/bin/vpnkit-expose-port -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.17.0.2 -container-port 80 $ netstat -anp | egrep ':::80' # tcp 0 0 :::80 :::* LISTEN 3404/vpnkit-expose- $ echo -en "GET / HTTP/1.0\n\n" | nc localhost 80 | grep 'Welcome to nginx!' # <title>Welcome to nginx!</title> # <h1>Welcome to nginx!</h1>通常、Docker Daemon は iptables の NAT Table に Static な Forwarding 設定を追加する事で Port Forwarding を実現するが、起動時に
--userland-proxy-path
オプションを渡すことで、独自の Userland Proxy を使うようすることができる。
( とはいえ、互換性を考慮してか、現在は vpnkit-iptables-wrapper が代わりに呼ばれ、iptables を変更しつつ vpnkit-expose-port も起動するようだ )$ ps | grep dockerd # 1291 root 7:56 /usr/local/bin/dockerd -H unix:///var/run/docker.sock --config-file /run/config/docker/daemon.json --swarm-default-advertise-addr=eth0 --userland-proxy-path /usr/bin/vpnkit-expose-portまた、
vpnkit-expose-port
は起動時に/port
下に[Src Protocol]:[Src IP]:[Src Port]:[Dest Protocol]:[Dest IP]:[Dest Port]
というフォルダを作成する事で、9p 経由でvpnkit.exe
へと Port Forwarding 情報を伝える。$ ctr --namespace services.linuxkit tasks exec --exec-id 1000 docker ls /port # README # tcp:0.0.0.0:80:tcp:172.17.0.3:80Port Forwarding 情報を受けた
vpnkit.exe
は、自身が Port Forwarding するその Port で Listen し始める。
ここで、Windows Host からlocalhost:80
にアクセスすると、まずvpnkit.exe
に connect され、vpnkit.exe
内で Multiplexing, Encapsulation されてhyperv-connect://<<DockerDesctopVM>>/0000F3A5-FACB-...
へ向けて送信される。
vpnkit-forwarder
がvsock(cid=VMADDR_CID_ANY, Port=0000F3A5)
で待ち受けており、受け取ったデータを Decapsulation, Demultiplexing し、後は Forward Proxy として[Dest IP]:80
にアクセスする。( ん、Dest IP 指定するなら
vpnkit-expose-port
の Listen 要らないのでは ? ここ とか ここ とか ここ 見ると Dest IP 教えてるっぽい )ちなみに 9p をわざわざ使っているのは、
vpnkit-expose-port
が起動中/port/XX:XX:XX:XX:XX:XX
File Descriptor をわざと Open したままにしておくことで、Crush や Kill された際に 9p のclunk
Message が vpnkit へ通知され、Leak を防ぐことができる為らしい。● Windows Named Pipe
Windows には、Named pipe ( 日本語で、名前付きパイプ ) と呼ばれるプロセス間通信の方法がある。
Unix にも同名の概念があるが、Windows の場合は以下の特徴がある。
- ファイル実体はなく、NPFS ( named pipe filesystem ) 上に mount される
\\.\pipe\PipeName
- 揮発性で、通信プロセスが止まれば消える
- Windows で Unix Domain Socket の代わりとして選択されるケースが多い
● \\.\pipe\docker_engine
com.docker.proxy
が Docker API Call を待ち受けている Named Pipe。
Docker Client が繋ぎに行っている。
\\.\pipe\docker_engine_windows
というのもあるが、こっちは Windows の dockerd へと繋がっている。PS> docker -H "npipe:////./pipe/docker_engine" info | wsl grep OSType # OSType: linux PS> docker -H "npipe:////./pipe/docker_engine_windows" info | wsl grep OSType # OSType: windows● \\.\pipe\dockerVpnKitControl
vpnkit.exe
起動時に、9p Control 用待受アドレスとして渡される 2 つのアドレスの内の 1 つ。vpnkitexe起動パラメータvpnkit.exe .... --port //./pipe/dockerVpnKitControl --port hyperv-listen://00000000-0000-0000-0000-000000000000/0000F3A5-FACB-11E6-BD58-64006A7986D3 .....通常は、
hyperv-listen://00000000-0000.../0000F3A5-FACB-...
経由で操作されるはずだが、誰か Windows 側でも繋いでいるのかもしれない。● \\.\pipe\dockerVpnKitDiagnostics
vpnkit.exe
起動時に診断用待受アドレスとして渡される。vpnkitexe起動パラメータvpnkit.exe ..... --diagnostics \\.\pipe\dockerVpnKitDiagnostics ....多分 ここ に書かれている診断用データを流すための Named Pipe と思われる。
The active ports may be queried by connecting to a Unix domain socket on the Mac or a named pipe on Windows and receiving diagnostic data in a Unix tar formatted stream.
試しに繋いでみると、すごい勢いで謎の Binary ( 多分 Tar 圧縮されている ) が流れてくる。
● \\.\pipe\dockerLogs
Windows 側で Log を集約するための Endpoint と予想。
送ってみたが接続数限界らしい。なので未確認。$ echo 'hoge' > \\.\pipe\dockerLogs # out-file : すべてのパイプ インスタンスがビジーです。 # 発生場所 行:1 文字:1 # + echo hoge > \\.\pipe\dockerLogs # + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # + CategoryInfo : OpenError: (:) [Out-File], IOException # + FullyQualifiedErrorId : FileOpenFailure,Microsoft.PowerShell.Commands.OutFileCommand● \\.\pipe\dockerDockerDesktopVM-com1
名前の通りなら COM Port。
のはずだけど、VM の設定見ても COM Port 無いんだよなぁ。謎。
● DNS
Docker Desktop によって、
C:\Windows\System32\drivers\etc\hosts
に以下が追加されている。
IP ADDRESS の部分には、Host の Default Route の IP が入っている。hosts... # Added by Docker Desktop [[IP ADDRESS]] host.docker.internal [[IP ADDRESS]] gateway.docker.internal # End of sectionただ、Wifi の繋ぎ直し等をして Network 環境が変わっても書き換えられない。
Linuxkit 側では、
192.168.65.0/28
の Default gateway と Windows Host と思しき相手が設定されている。
どちらもvpnkit-tap-vsockd
の作る仮想的な Network 内の Node だ。$ nslookup gateway.docker.internal # nslookup: can't resolve '(null)': Name does not resolve # # Name: gateway.docker.internal # Address 1: 192.168.65.1 $ nslookup host.docker.internal # nslookup: can't resolve '(null)': Name does not resolve # # Name: host.docker.internal # Address 1: 192.168.65.2 $ ip a show dev eth0 # 5: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN qlen 1000 # link/ether 02:50:00:00:00:01 brd ff:ff:ff:ff:ff:ff # inet 192.168.65.3/28 brd 192.168.65.15 scope global eth0 # valid_lft forever preferred_lft forever # inet6 fe80::50:ff:fe00:1/64 scope link # valid_lft forever preferred_lft forever $ ip n show dev eth0 # 192.168.65.1 lladdr f6:16:36:bc:f9:c6 ref 1 used 0/0/0 probes 1 REACHABLE # 192.168.65.2 lladdr f6:16:36:bc:f9:c6 used 0/0/0 probes 4 STALE● Diagnosis
Log : Docker Desktop
Docker Desktop が出しているログ。
C:\Users\username\AppData\Local\Docker
以下に出力される。
以下、代表的な出力元。● Moby
Linuxkit カーネルのログと LinuxKit の初期化処理のログが出力されている。
どの経路で Linux から Windows 側に送られているのかは知らない。● VpnKit
vpnKit.exe
のログ。LinuxKit 側のforwarder
等のログは無いようだ。● HyperV
Hyper-V の操作ログ。● ApiProxy
com.docker.proxy.exe
のログと思われる。主に Linux 側の Docker Daemon への指示とその返信が出力される。● NamedPipeServer/NamedPipeClient
ログを見ると、バージョンを送ったり、VM のディスクサイズを送ったり、engine スタートしろと指示を出したりしている。
重要な仕事をしてそうなのだが、誰が Server で誰が Client なのか不明。Log : LinuxKit
LinuxKit のログ。
普通に LinuxKit Host の/var/log
以下にある。
OS は Read-Only のはずだが、/var/log
は/var/lib/log
の Alias になっている。まとめ
Docker + Kubernetes 環境となると、どうしても L2 ~ L3 辺り動的でかつ複雑になるのは避けられなくて、そんな中でも確実に通信経路を確保するためには、やはり Unix Domain Socket や Named Pipe の様なプロセス間通信が有効になるのかなと思いました。
Docker の情報というと、入門と How To と Linux 要素技術との関係性が多いので、少し違う視点からのまとめとしても役に立てば良いなぁと思います。
次回に続く。
おまけ
NIC が、物理 NIC なのか、Bridge なのか、TUN/TAP なのか、
ethtool
が無い環境でどう調べる方法
- Physical devices -
/sys/class/net/eth0/device
があるかどうか- Bridges -
/sys/class/net/br0/bridge
があるかどうか- TUN and TAP devices -
/sys/class/net/tap0/tun_flags
があるかどうか参考 : How to know if a network interface is tap, tun, bridge or physical?
Kubernetes が起動しない
Error while setting up kubernetes: cannot update the host kube config: Failed to load Kubernetes CA: couldn't load the certificate file C:\ProgramData\DockerDesktop\pki\ca.crt: open C:\ProgramData\DockerDesktop\pki\ca.crt: Access is denied
一旦 Windows のエクスプローラで
C:\ProgramData\DockerDesktop\pki\
を開くと『このフォルダにアクセスする権限がありません』が出るので、これで『続行』を押せば、それ以降アクセスできるようになる。Error while setting up kubernetes: cannot update the host kube config: cannot load current kubernetes config: Error loading config file \"C:\Users\username\.kube\config\": yaml: control characters are not allowed.
C:\Users\username.kube\config を一旦リネームすると、新たに作り直されて解決する。
参考
- 投稿日:2019-03-18T01:28:29+09:00
GriveでGoogleドライブをLinuxマシンと同期する
Googleドライブは便利ですが、自動でファイルエクスプローラーからアクセスできる機能は、WindowsやAndroid等でしか提供されておらず、Linuxマシンをデスクトップとして使っている場合不便です。
ここでは、Googleドライブのディレクトリの内容を、ローカルの特定ディレクトリの内容と同期して、ファイルをいい感じに移動させたりしたいと思います。
Grive2のインストール
griveは更新が2年ほど前で終わっていますが、有志がフォークしてgrive2を作っているのでそれを使います。
https://github.com/vitalif/grive2 のREADME.mdやwikiに書いてあるとおり、CMakeでビルドしてインストールするのですが、自分はArchlinuxのAURがあったのでそれを使いました。
ディレクトリの設定
cd $HOME mkdir google-drive cd google-drive grive -a
grive -a
を実行すると、URLが表示されるので、コピーしてブラウザで開きます。
開くと、Googleの認証画面でGoogle Driveへのアクセス権を尋ねられるので、連携or許可をクリックします。そうすると、認証キーが表示されるのでターミナルに貼り付ければ完了です。
あとは自動でGoogleドライブ上の全ファイル・ディレクトリがダウンロードされます。
自動同期
systemctl --user enable grive-timer@$(systemd-escape google-drive).timer systemctl --user start grive-timer@$(systemd-escape google-drive).timer systemctl --user enable grive-changes@$(systemd-escape google-drive).service systemctl --user start grive-changes@$(systemd-escape google-drive).service上の例では、
google-drive
という名前のホームディレクトリ直下のディレクトリを同期しています。
grive-timer@.timer
で5分間隔の自動ダウンロード、grive-changes@.service
でinotifyファイル検知を使って自動アップロードをしています。これで、Google Driveでファイルの作成/削除やローカルでファイルの作成/削除をすると、同期されるようになりました。
※AURにはsystemdのファイルがなかったので、ソースから.inを直接ダウンロードして手直しして入れました。
まとめ
内容が、README.mdを日本語訳しただけなので、ほどんど「いかがでしたかブログ」並に薄くなってしまいましたが、役に立てば嬉しいです。
今回のgrive2は、必要になったらファイルの実態をダウンロードする方式(google drive ocamlfuse mount)とは違い、単純にダウンロードして差分同期を取る方式でした。
自分はネットワーク帯域もストレージ帯域も余裕が有り、こちらのほうが便利で使いやすかったのですが、これは人によって異なると思うので、両方試して自分が使い勝手の良い方を使ってみると良いと思います。