拾い物のコンパス

まともに書いたメモ

PCのBluetoothバージョンを確認するコマンド(Arch Linuxを例に)

Bluetooth 5.0は速いと聞く.
でもPC・イヤホンなどのBluetooth機器が両方ハード・ソフト的に対応していないと使えないらしい.
ソフトはアップグレードできるから,とりあえずPCのハードはどのバージョンなのかを確かめたメモ.

コマンド郡

コマンドの流れは以下の通り.
1. bluetooth関連のパッケージ(bluez, bluez-utils)をインストール
2. buetoothデーモンを実行(modprobe, systemctl)
3. bluetoothバイスを有効化(対話式コマンドbluetoothctlで操作)
4. 3.で起動したbluetoothバイスの詳細情報からバージョンを確認
恥ずかしいからMACアドレス?にしてある.

  # 1.bluetooth関連のパッケージ(`bluez`, `bluez-utils`)をインストール
  $ sudo pacman -S bluez bluez-utils

  # 2. buetoothデーモンを実行(`modprobe`, `systemctl`)  
  $ sudo modprobe btusb
  $ sudo systemctl start bluetooth # デーモン実行
  $ sudo systemctl enable bluetooth # サービスの有効化

  # 3. bluetoothデバイスを有効化(対話式コマンド`bluetoothctl`で操作)  
  $ which bluetoothctl # コマンド有無
  $ bluetoothctl help # コマンドの確認
  $ bluetoothctl # 対話式
  Agent registered
  [CHG] Controller ??:??:??:??:??:?? Pairable: yes
  [bluetooth]# agent KeyboardOnly
  [bluetooth]# default-agent
  [bluetooth]# power on
    [CHG] Controller ??:??:??:??:??:?? Class: 0x000c010c
    Changing power on succeeded
  
    [CHG] Controller ??:??:??:??:??:?? Powered: yes
  [bluetooth]# scan on
  [bluetooth]# devices # 近くにあるデバイス一覧表示(何らかのデバイスが出てくればOK)
  [bluetooth]# quit

  # 4. 3.で起動したbluetoothデバイスの詳細情報からバージョンを確認  
  $ hciconfig -a
  hci0:   Type: Primary  Bus: USB
        BD Address: ??:??:??:??:??:??  ACL MTU: 1021:4  SCO MTU: 96:6
        UP RUNNING PSCAN ISCAN
        RX bytes:15896 acl:0 sco:0 events:2476 errors:0
        TX bytes:596459 acl:0 sco:0 commands:2466 errors:0
        Features: 0xff 0xfe 0x0f 0xfe 0xdb 0xff 0x7b 0x87
        Packet type: DM1 DM3 DM5 DH1 DH3 DH5 HV1 HV2 HV3
        Link policy: RSWITCH HOLD SNIFF
        Link mode: SLAVE ACCEPT
        Name: '????????????'
        Class: 0x0c010c
        Service Classes: Rendering, Capturing
        Device Class: Computer, Laptop
        HCI Version: 4.2 (0x8)  Revision: 0x100
        LMP Version: 4.2 (0x8)  Subversion: 0x100
        Manufacturer: Intel Corp. (2)

最後のコマンド結果にあるLMP Version: 4.2...というところがBluetoothのバージョンに当たる.
LMP (Link Manager Protocol) のバージョンとBluetoothの対応付は以下の通り.

LMP (Link Manager Protocol) バージョン Bluetooth コア仕様
LMP 0 Bluetooth コア仕様 1.0b (使用中止)
LMP 1 Bluetooth コア仕様 1.1 (使用中止)
LMP 2 Bluetooth コア仕様 1.2 (使用中止)
LMP 3 Bluetooth コア仕様 2.0 + EDR (使用中止)
LMP 4 Bluetooth コア仕様 2.1 + EDR (非推奨、使用中止予定)
LMP 5 Bluetooth コア仕様 3.0 + HS (非推奨、使用中止予定)
LMP 6 Bluetooth コア仕様 4.0
LMP 7 Bluetooth コア仕様 4.1
LMP 8 Bluetooth コア仕様 4.2
LMP 9 Bluetooth コア仕様 5.0
LMP 10 Bluetooth コア仕様 5.1
LMP 11 Bluetooth コア仕様 5.2tel Corp. (2)

今回自分のPCは4.2.つまりBluetooth コア仕様 2.1 + EDR (非推奨、使用中止予定)
最新機種のイヤホンなどはほぼその性能を活かせない.ウソみたいだろ?

所感

Bluetooth機器を買う夢は始まる前から終わっていた.
LMPの対応表を見つけるまではLMP=Bluetoothでなんだ4.2対応機種を探せばいいのかーくらいに軽く考えていた.

もしかして最新のAirPodsが接続できなかったのはこれが原因だろうか・・・.
調べた感じだとプロファイルがきっちりしていれば接続自体はできるとあったが,それでも高速伝送などの面で劣ることに変わらない.
次の手段を考えるかー.
まだできることはあるはずだ.

参考

Bluetooth - ArchWiki

最新のBluetooth規格とアップグレード方法 | ゲーミングPC辞典

https://support.microsoft.com/ja-jp/help/4524769/windows-10-what-bluetooth-version-is-on-my-device

情報系カンファレンス格付けサイトCOREを知った話

カンファレンスの格付けが素早く調べられるCOREを知った話.

背景

研究の流行やState Of The Art(SOTA)を知りたいときはトップカンファレンスの論文を探すことから始めるようにしている.
限られた時間で質の良い論文をすばやく探すのは重要だ.

課題

論文を探す上で以下の課題に悩まされていた.

  • 無数にあるカンファレンスの中でどれがトップなのか悩む
  • 効率が悪い(検索アルゴリズムはbrute force, O(n)?)
    (とりあえず知っているACM CCSの論文で参照されている会議の論文をあたる
    ACMの公式サイトにあるカンファレンスを総当り)

インパクトファクターもあるけど生物系の評価基準でコンピュータ・サイエンスには使いにくい気がする(個人の意見です).

格付けサイトを使う利点

今回紹介するCORE(に限らず)格付けサイトを使えば以下の利点がある.

  • 無数にあるカンファレンスの中でどれがトップなのか悩む
    → トップカンファレンス(格付けサイト内で評価が高い)の把握ができる
    質の良い論文が揃っているカンファレンスを素早く把握できる
  • 効率が悪い(検索アルゴリズムはbrute force)
    → 調べる対象を減らすことができる(O(n)的なのは変わらないかも・・・)

上記に加えて自分が知らない領域でも読むべきカンファレンスの絞り込みができる.

Computing Research & Education(CORE)

本題.
Computing Research & Education(以降CORE)というサイトを紹介.
オーストラリアとニュージーランドの大学により運営されている.
コンピュータ・サイエンスに特化しており,分野を限って調べたいときは都合が良い.
ただし,Unrankedなカンファレンスも多いため参考にできないこともある.
Documentも2020年版が掲載されているため,情報も新しそうだ.

格付け基準

カンファレンスの格付けは*A/A/B/C/unranked.
詳細はここに記載.

迷ったら*A/Aを当たれば良さげ.
内部ではJournal Citation ReportsとScopusの評価を参考にしているようだ.

使い方

ここに会議名を入れるだけ.
dimvaってトップカンファレンスと思っていたけどC評価だった.
ええ・・・?

所感

格付けサイトの回し者みたいな記事になってしまった・・・.
調べはじめの方針決めに使うのが良さげ.
実際は他の分野も総括して調べられるScopus(使ったことはない)を使うのが良いのかな.

余談になるが,会計処理も公開していた.
2019年は$45,234.21, 日本円に換算すると480万円.
サイト運営も楽じゃないようだ.

参考

Computing Research & Education

Filesystem Hierarchy Standardを読む

Linuxに出会ってから10年ほど経つが標準文書を読んだことはなかった.
まずはファイルシステムについて規定されたFilesystem Hierarchy Standardを理解がてらまとめる.
適宜省略しているため必要に応じて原文を読んでください.

Linuxの標準について

Linuxの規定はLinux Standard Base(以後LSB)に記載されている.
目的: LinuxディストリLSBの目標は、Linuxディストリビューション間での互換性を向上させ、準拠システム上でのアプリケーションの動作を保証するよう標準規格を策定・振興することである。さらに、ソフトウェアベンダーがLinux向けに製品を移植したり開発する際の調整努力を助ける。(Wikipediaより)

最新は5(2020/05/17現在)Coreドキュメントは964ページある.
基本的なライブラリlibc関係とかよくしらなかったがそんな疑問もこれを読めばとっかかりとなりそう.

Filesystem Hierarchy Standard

ようやく本題.3.0(50 page)を読み進めていく.
Linuxの必要なファイル・ディレクトリ配置を記載した文書.
目的はアプリケーション,管理ツールなどの互換性を図るもの.

文書の目的(Chapter1 Introduction Purpose)

ユーザ・ソフトがインストールされているファイルやディレクトリの場所を予想できるようにすること.

Root Filesystem(Chapter3)

root filesystemはboot/restore/recover/repairのために必要なものを格納する.

必要なディレクトリ一覧

ディレクトリ名 詳細
bin 必須コマンドバイナリ置き場
boot ブートローダ用の静的ファイル
dev バイスファイル
etc ホスト特有の設定ファイル
lib 必須共有ライブラリとカーネルモジュール
media removable meidaのマウントポイント
mnt 一時的なファイルシステムのマウントポイント
opt Add-onアプリ置き場
run running processesのデータ関連
sbin 必須システムバイナリ
srv システムから提供されるサービスのデータ
tmp 一時ファイル
usr Secondary Hierarchy
var Variable data
以下,オプション
home ユーザ用ホームディレクト
lib 必須な共有ライブラリの代替
root root用のホームディレクト

bootableなLinuxを作ろうと思ったら,最低限上記のディレクトリを揃える必要があるということか.

ここでは省略するが,各ディレクトリの詳細も記載されている.
/usr/share/colorとか初耳なディレクトリもある.

/var Hierarchy(Chapter5)

可変データファイル置き場. スプールディレクトリ,ファイル,管理ログも含まれる.

必要なディレクトリ一覧

ディレクトリ名 詳細
cache キャッシュデータ
lib 状態情報
local /usr/localの情報
lock lock files
log ログファイル関連
opt /opt用の情報
run running processes関連のデータ
spool アプリのspool data(syorimati data)
tmp 一時ファイル
以下,オプション
account process accounting logs
crach system crash dumps
games variable game data
mail user mailbox files
yp Network Information Service database files

/var/lockは複数のアプリで共有されているデバイスやリソースを管理するために使われる.

所感

文書によっては2770ページという頭が真っ白になりそうな量もあるが,調べたい領域を絞れば数十ページで済む.
読み始めてしまえばわかりやすい英語でスラスラ読めた.

今回読んだFHSには各ディレクトリ全てについて何に使うために存在するかが書かれている.
何度も繰り返し読みながら理解を進めていきたい.
spoolは処理待ちの一時ファイルを格納する場所初めて知ることもあってまだまだ勉強不足だと思い知った.
kernel開発者になる道は遠い. 一歩々々進んでいきたい.

参考

Linux Standard Baseとは

FHS本文

規定文書リンクサイト

VagrantでRust製デバドラを動かす環境を作る話

CTF問題でもない限り好んでUse After Freeバグを作りたくはない.
その点Rust(ラストと読むと最近知った)はC/C++に比べてメモリ管理について優れていると言われている.
低レベル操作も充実していて速度もC/C++並に出るときもある. Rustのチュートリアルを一通り読み終わったのでVagrant上でデバドラ開発環境を整えた.

やりたかったこと

よりバグを少なくデバドラ開発がしたい!
Goodbye, unsafe C/C++
ヒャッハー,開発中のバグでホストがカーネルパニックしたくないから仮想環境の中に閉じ込めだぁ!

ステップ

  1. Vagrantを立ち上げる

  2. C言語のHello, Worldなデバドラのコンパイル・実行

  3. Rust製のHello, Worldなデバドラのコンパイル・実行

C言語でも確認するのは必要なモジュールがインストールできているかの確認用.
Rust製のHello, Worldなデバドラはこれを使った.
Dockerで環境を作ることも考えたが,以下の点で向いていないと判断.

やるまえから嫌な予感はしつつ挑戦してみた.
「上記の問題解決を頑張るのが目的じゃないから違う方法を考えよう.」
そう気づいた頃にはDockerイメージ作成してから3時間経っていた.もっと早く気づけ.
今回はUbuntuのイメージで環境を作っていったが,Ubuntuの標準レポジトリからインストールできるlinuxソースのバージョンとホストOSのバージョンが違うと本当に面倒くさい.
レポジトリ追加とかやったら解決できるけどdmesgできない問題が口を開けて待っている.
良い子のみんなはフルエミュレーションな仮想環境が一番と心に刻んでほしい.

環境構築

Vagrantのインストールは探せばいくらでも出てくるから省略する.
$ sudo pacman -S vagrant
的なことをすれば良い.

$ vagrant --version
Vagrant 2.2.6

Vagrantを立ち上げる

以下をVagrantfileとして保存.

# frozen_string_literal: true

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

# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure('2') do |config|
  # The most common configuration options are documented and commented below.
  # For a complete reference, please see the online documentation at
  # https://docs.vagrantup.com.

  # Every Vagrant development environment requires a box. You can search for
  # boxes at https://vagrantcloud.com/search.
  config.vm.box = 'generic/ubuntu1810'

  # Disable automatic box update checking. If you disable this, then
  # boxes will only be checked for updates when the user runs
  # `vagrant box outdated`. This is not recommended.
  # config.vm.box_check_update = false

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine. In the example below,
  # accessing "localhost:8080" will access port 80 on the guest machine.
  # NOTE: This will enable public access to the opened port
  # config.vm.network "forwarded_port", guest: 80, host: 8080

  # Create a forwarded port mapping which allows access to a specific port
  # within the machine from a port on the host machine and only allow access
  # via 127.0.0.1 to disable public access
  # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"

  # Create a private network, which allows host-only access to the machine
  # using a specific IP.
  # config.vm.network "private_network", ip: "192.168.33.10"

  # Create a public network, which generally matched to bridged network.
  # Bridged networks make the machine appear as another physical device on
  # your network.
  # config.vm.network "public_network"

  # Share an additional folder to the guest VM. The first argument is
  # the path on the host to the actual folder. The second argument is
  # the path on the guest to mount the folder. And the optional third
  # argument is a set of non-required options.
  config.vm.synced_folder './', '/vagrant_data'

  # Provider-specific configuration so you can fine-tune various
  # backing providers for Vagrant. These expose provider-specific options.
  # Example for VirtualBox:
  #
  # config.vm.provider "virtualbox" do |vb|
  #   # Display the VirtualBox GUI when booting the machine
  #   vb.gui = true
  #
  #   # Customize the amount of memory on the VM:
  #   vb.memory = "1024"
  # end
  #
  # View the documentation for the provider you are using for more
  # information on available options.

  # Enable provisioning with a shell script. Additional provisioners such as
  # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the
  # documentation for more information about their specific syntax and use.
  # config.vm.provision 'shell', inline: <<-SHELL
  #  apt update
  #  apt install -y install git gcc curl make kmod linux-source linux-headers-$(uname -r) build-essential libelf-dev
  #  apt install llvm clang
  # SHELL
end

$ vagrant initで生成されるファイルほぼそのまま.
いじったのはconfig.vm.boxUbuntu 18.10を選択したこととホストとの共有フォルダとしてVagrantfileがあるディレクトリを/vagrant_dataとしてマウントする設定を加えた.
SHELLは無効化中のため割愛.

Vagrant環境内に開発環境を持ち込むと設定がまた時間がかかるから共有フォルダ経由で編集はホスト,コンパイル・検証は仮想環境内に切り分けた.
起動してログインする.

$ vagrant up
$ vagrant ssh
vagrant@ubuntu1810:/$

環境とバージョンは以下の通り.

$ uname -a
Linux ubuntu1810.localdomain 4.18.0-25-generic #26-Ubuntu SMP Mon Jun 24 09:32:08 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

まずは必要なパッケージをインストール.

$ apt-get update
$ apt-get install -y install git gcc curl make kmod linux-source linux-headers-$(uname -r) build-essential libelf-dev
$ ls /lib/modules/`uname -r`/build # 必要なものがある確認
arch   crypto         firmware  init    Kconfig  Makefile        net      security  tools   virt
block  Documentation  fs        ipc     kernel   mm              samples  sound     ubuntu  zfs
certs  drivers        include   Kbuild  lib      Module.symvers  scripts  spl       usr

$ gcc --version
gcc (Ubuntu 8.3.0-6ubuntu1~18.10.1) 8.3.0
$ make --version
GNU Make 4.2.1

作業用ディレクトリ(共有フォルダ)に移動.

$ cd /vagrant_data
$ ls
Vagrantfile

これで最低限の環境は整った.

C言語のHello, Worldなデバドラのコンパイル・実行

C言語でのデバイスドライバコンパイル・実行できるかを確認する.
ソースコードMakefileを用意する.

// hello.c
#include <linux/module.h>

MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("poppycompss <t0g0v31dk@gmail.com>");
MODULE_DESCRIPTION("Hello world kernel module");

static int km_init(void)
{
    printk(KERN_INFO "Hello my module\n");
    return 0;
}
static void km_exit(void)
{
    printk(KERN_NOTICE "Goodbye my module\n");
}

module_init(km_init);
module_exit(km_exit);

起動時にHello my module, 終了時にGoodbye my moduleと出力するだけのデバドラ.
続いてMakefile

# Makefile
.PHONY: all clean
obj-m := hello.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD = $(shell pwd)
all:
        make -C  ${KDIR} M=$(PWD) modules
clean:
        make -C  ${KDIR} M=$(PWD) clean

コンパイルに必要なファイルを作業用ディレクトリにコピー.

$ cp /lib/modules/`uname -r`/build/.config ./
$ cp /lib/modules/`uname -r`/build/Module.symvers ./

準備はここまで.コンパイルする.

$ make
make -C  /lib/modules/4.18.0-25-generic/build M=/vagrant_data modules
make[1]: Entering directory '/usr/src/linux-headers-4.18.0-25-generic'
  CC [M]  /vagrant_data/hello.o
  Building modules, stage 2.
  MODPOST 1 modules
  LD [M]  /vagrant_data/hello.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.18.0-25-generic'

hello.koが生成されているはず.

$ sudo insmod hello.ko
$ sudo rmmod hello
$ dmesg | tail
[21061.045821] Hello my module
[21061.054198] Goodbye my module

これでC言語のデバドラが動いていることが確認できた.
次からが本題.

Rust製のHello, Worldなデバドラのコンパイル・実行

必要なパッケージのインストール

$ sudo apt install -y llvm clang
$ clang -v
clang version 7.0.0-3 (tags/RELEASE_700/final)
Target: x86_64-pc-linux-gnu
...(snip)...

Rustをローカルにインストールする.

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

もし上記がうまく行かなければ

$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs > rust-installer.sh
$ chmod +x ./rust-installer.sh
$ ./rust-intsaller.sh

プロンプトで「どれをインストールする?」って聞かれたら'1'を入れた.
インストールが終わったらパスを通す

$ source $HOME/.cargo/env

これでrustupコマンドが使えるようになる.
今回動かそうとしているデバドラにはRustのnightlyを使う必要があるからインストールして切り替える.
切り替えはデバドラの作業ディレクトリで実施した.

$ rustup install nightly
$ git clone https://github.com/fishinabarrel/linux-kernel-module-rust
$ cd linux-kernel-module-rust/hello-world
$ rustup toolchain list # 現在使っているバージョンを確認
stable-x86_64-unknown-linux-gnu (default)
nightly-x86_64-unknown-linux-gnu
$ rustup override set nightly # nightlyに切り替え
$ rustup component add rust-src rustfmt
$ rustup --version
rustc 1.41.0 (5e1a79984 2020-01-27)

ようやくmakeできると思いきやいろんなエラーにぶつかる (ぶつからなかった人はそのまま次に進んでください)

$ make
// エラーのみ抜粋
error[E0308]: mismatched types
  --> /vagrant_data/linux-kernel-module-rust/src/allocator.rs:13:41
   |
13 |         bindings::krealloc(ptr::null(), layout.size(), bindings::GFP_KERNEL) as *mut u8
   |                                         ^^^^^^^^^^^^^ expected `u64`, found `usize`
   |
help: you can convert an `usize` to `u64` and panic if the converted value wouldn't fit
   |
13 |         bindings::krealloc(ptr::null(), layout.size().try_into().unwrap(), bindings::GFP_KERNEL) as *mut u8
   |                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0308]: mismatched types
   --> /vagrant_data/linux-kernel-module-rust/src/file_operations.rs:193:28
    |
193 |         self.0.read = Some(read_callback::<T>);
    |                            ^^^^^^^^^^^^^^^^^^ expected `u64`, found `usize`
    |
    = note: expected fn pointer `unsafe extern "C" fn(_, _, u64, _) -> i64`
                  found fn item `unsafe extern "C" fn(_, _, usize, _) -> isize {file_operations::read_callback::<T>}`

error[E0308]: mismatched types
   --> /vagrant_data/linux-kernel-module-rust/src/file_operations.rs:200:29
    |
200 |         self.0.write = Some(write_callback::<T>);
    |                             ^^^^^^^^^^^^^^^^^^^ expected `u64`, found `usize`
    |
    = note: expected fn pointer `unsafe extern "C" fn(_, _, u64, _) -> i64`
                  found fn item `unsafe extern "C" fn(_, _, usize, _) -> isize {file_operations::write_callback::<T>}`

error[E0308]: mismatched types
   --> /vagrant_data/linux-kernel-module-rust/src/sysctl.rs:136:36
    |
136 |                 proc_handler: Some(proc_handler::<T>),
    |                                    ^^^^^^^^^^^^^^^^^ expected `u64`, found `usize`
    |
    = note: expected fn pointer `unsafe extern "C" fn(_, _, _, *mut u64, _) -> _`
                  found fn item `unsafe extern "C" fn(_, _, _, *mut usize, _) -> _ {sysctl::proc_handler::<T>}`

error: aborting due to 4 previous errors

For more information about this error, try `rustc --explain E0308`.
The following warnings were emitted during compilation:
error: could not compile `linux-kernel-module`.

bindgenのバージョンによる変更の影響らしい. Cargo.tomlを修正する.

[-] bindgen = "*"
[+] bindgen = "0.51.0"
$ make

コンパイルが成功したら読み込んでみる.

$ sudo insmod helloworld.ko
$ sudo rmmod helloworld
$ dmesg | tail
...(snip)...
[40960.460642] Hello kernel module!
[40960.468459] My message is on the heap!
[40960.468460] Goodbye kernel module!
...(snip)...

動いた.

所感

dockerはデバドラ開発には向いてない(確信)

実はRust製デバドラとしてはよりシンプルなものもあるがこっちもエラーにぶつかった. 今後の開発を考えると今回使ったやつのほうが実装されている部分が多くて参考になりそうだったからデバッグはしてない.
開発は慣れるまでCで書いて動作確認しつつRustに書き直す作業になりそうだ.

参考

「Rust」言語はCよりも遅いのか、研究者がベンチマーク結果を解説:モダンCPUでは性能低下は軽微 - @IT

GitHub - fishinabarrel/linux-kernel-module-rust: Framework for writing Linux kernel modules in safe Rust

GitHub - tsgates/rust.ko: A minimal Linux kernel module written in rust.

centos7 - Running dmesg on Docker results in "dmesg: read kernel buffer failed: Permission denied" - Server Fault

Linux環境でのインストールスクリプト確認用にdocker-composeを整備する

開発したプログラムが複数の環境上で動くかどうかの確認をしたいときがある.
例えばUbuntu, CentOSはバージョン・ディストリの特徴によって環境が僅かに違うが,新しいOSをインストールした際の設定自動化スクリプトはどんな場所でも動いてほしい.
例えば自分が作成したプログラムのコンパイル・インストールをどんな環境でも動くスクリプト1つ実行する形にまで仕上げたいとき
しかしこれを全環境の仮想マシンを用意してチェックしていくのは面倒だ.
というわけでdocker-composeを使って一斉に各ディストリで動くかどうかをチェックする環境を作った話.

やりたかったこと

最初に書いた理由も再掲.

  1. 作ったプログラムが各ディストリ・バージョン上で動くかどうか簡単に確かめたい
  2. 開発環境で動くようになったがまっさらな環境で動くのか確認したい
  3. 上記を一斉に行いたい

とうわけでコンパイル・インストールを確認する環境を作り,自分の環境構築用スクリプトが動くかどうかを確かめた.

何を使って実現するか

他の人がどうやっているのか知らないがいくつか案を練った.

  1. Virtualboxを複数立てて(vagrantを使う?)SSHで同時接続
  2. コンテナ(docker-compose, Kubernetes)を使う
  3. ラズパイをたくさん買ってPCを複数接続できるキーボードを用意して頑張る(ド鬼畜)

3.は財布的・手間的にない.1.は理想的だが手間がかかる.
2.が現実的かな.
docker-composeKubernetesを比較すると後者のほうが大規模の構成ができそうだ.
が,今回の用途だとそこまで複雑な実装は不要そう.
zozoのブログが非常に刺激になり,現実的な実装を思いつけた.

考えたアーキテクチャ

考慮した結果こうなった.

f:id:poppycompass:20200205213220p:plain
イメージ
開発者は端末から最初コマンドを実行すればあとはコンテナを立ち上げ,各コンテナ内での標準入出力をログとしてホスト上に保存する.
ファイル構成はこんな感じ.

|--dists # 各ディストリ設定を置く
|  |--ubuntu
|  |  |--Dockerfile
|  |  |--personal-settings.yml
|--docker-compose.yml # 共通設定
|--.env # 環境変数
|--logs # 各コンテナからのログの保存先
|  |--ubuntu.log # ubuntuコンテナからのログ(dists/???は???.logというファイルを作る)
|--run_env_test.sh # テスト実行

コンテナの追加はシンプルにしたかった.
考えた結果,追加はdists以下にコンテナ用の設定ディレクトリを追加するだけで良いようにした.
$ cp dists/ubuntu dists/centosとかして,中のファイルをいじれば良い.

各ファイルの中身

dists/<ディストリ>/Dockerfile

例はubuntuの場合で説明.

FROM ubuntu # 好きなコンテナを指定.他の設定もお好みで追加.

dists/<ディストリ>/personal-settings.yml

ubuntuの場合.
各コンテナ固有の設定を書く. ホスト上のディレクトリをマウントして共有している.
commandのところに実行したいコマンドを入力する.
今入力されているのは環境構築用のスクリプト
最後のsleepコマンドはエラーが合った時にデバッグするためにbashを継続させる.

version: "3.7"
services:
  ubuntu:
    image: ubuntu
    tty: true
    container_name: 'ubuntu'
    environment:
      APP_ENV: ubuntu
    volumes:
      - ./dists/ubuntu/Dockerfile:/mnt/host/Dockerfile:ro
      - ./dists/ubuntu/personal-settings.yml:/mnt/host/personal-settings.yml:ro
      - ./logs:/mnt/host/logs
    command: bash -c 'apt -y update > /mnt/host/logs/ubuntu.log &&
                      apt -y install git >> /mnt/host/logs/ubuntu.log &&
                      git clone https://github.com/poppycompass/dotfiles >> /mnt/host/logs/ubuntu.log &&
                      cd dotfiles >> /mnt/host/logs/ubuntu.log &&
                      ./start.sh >> /mnt/host/logs/ubuntu.log &&
                      sleep 600'

volumes:
  ubuntu:
    driver_opts:
      type: none
      # 絶対パスで指定する
      device: /home/user/logs
      o: bind

.env

環境設定用ファイル.

UBUNTU=ubuntu

run_env_test.sh

テストを実行するために使うスクリプト
コンテナの完全初期化と実行というシンプルな内容.

#!/bin/sh
# installation test for each Linux distributions
TOPDIR="dists"
COMMON="docker-compose.yml"

readonly DEBUG=false
if "${DEBUG}"; then
  DISTS="ubuntu" # 1つずつの環境で実行したいとき
else
  DISTS=`ls ${TOPDIR}`
fi

# Run your installation process
function run () {
  for dist in ${DISTS}
  do
    # sudo docker ps
    # sudo docker exec -it <hash> bash
    sudo docker-compose -f ${COMMON} -f ${TOPDIR}/${dist}/personal-settings.yml -p ${dist} up -d
  done;
}

# Initialize all docker containers
function initialize () {
  sudo docker stop $(sudo docker ps -aq)
  sudo docker rm $(sudo docker ps -aq)
  sudo docker network prune -f
  sudo docker rmi -f $(sudo docker images --filter dangling=true -qa)
  sudo docker volume rm $(sudo docker volume ls --filter dangling=true -q)
  sudo docker rmi -f $(sudo docker images -qa)
}

# Show help
function show_help () {
  echo "Usage: ./run_env_test.sh [options]"
  echo "[options]: "
  echo "  run : run your installation process"
  echo "  init: initialize all container"
}

# Main routines
if [ "$1" == "run" ]; then
  run
elif [ "$1" == "init" ]; then
  initialize
else
  show_help
fi

実行

$ ./run_env_test.sh run
これで各コンテナが実行される.
$ tail -f logs/ubuntu.log
とかすればコンテナ内で何が行われているか把握できる.

初期化

コンテナをまっさらな状態にしたいときは
$ ./run_env_test.sh init

評価

やりたかったこと(再掲)

  1. 作ったプログラムが各ディストリ・バージョン上で動くかどうか簡単に確かめたい
    → 各ディストリ・バージョン環境の簡単な追加,コマンド実行ができるようになった!
  2. 開発環境で動くようになったがまっさらな環境で動くのか確認したい
    → コンテナを初期化することで毎回まっさらな環境で試せるようになった!
  3. 上記を一斉に行いたい
    → 一斉に起動可能!

最初に設定した要件は満たした.

改善が必要なところ

  • dockerは基本的にrootのため,user環境での実験作成
    add userからやるのであれば問題ないか?
  • プログラムを修正してからの再度実行に手間がかかる(Dockerの初期化・再起動させる必要がある.デバッグ時の細かい試し実行がしにくい)
    → 対策考え中

所感

しばらくはこれで運用しようと思う.
Kubernetesは勉強中のためこっちのほうが良さそうならまた同じような環境を作ろうと思う. もっと冴えたやり方を知っている方は是非教えてください.

参考

自動テストの実行環境をDockerでお気軽引っ越し - ZOZO Technologies TECH BLOG

Docker入門(第六回)〜Docker Compose〜 | さくらのナレッジ

複数のDockerコンテナを自動で立ち上げる構成管理ツール「Docker Compose」(Dockerの最新機能を使ってみよう:第7回) | さくらのナレッジ

Dockerfileでうまくマウントできないのでdocker-compose.yml使ってマウントする(o*。_。)o - Qiita

Docker Composeのトップレベルvolumesでホストのディレクトリをマウントする - ニューなんとなく書くブログ

docker-compose volumeマウント - Qiita

plantUMLを試してみる

自分には絵心がない.
加えて図を作るときはパワポに毎回素材のファイルを探して貼って線をなるだけ真っ直ぐに書いて,オブジェクトと線が離れていたことに後で気づいて微修正・・・.苦痛が伴う.
しかし図による理解は100行の文章に勝る.(正確に描かれていると嬉しい.間違ってても無いよりはまし)
plantUMLというコーディングするように各図を作成できるツールがあるようだったから使ってみた.
※注意:この記事はArch Linux上でplantUMLを使う方法に焦点を当てているためWindowsユーザにはあまり役に立たないかもしれません.

きっかけ

サイボウズNecoプロジェクトのスキルチェックシートを見ていて存在を知った.
図を作るときはよくもやっとするから気になった.
前置きはここまで.

環境

$ uname -a
Linux poppycompass 5.4.10-arch1-1 #1 SMP PREEMPT Thu, 09 Jan 2020 10:14:29 +0000 x86_64 GNU/Linux
$ java -version
openjdk version "1.8.0_232"
OpenJDK Runtime Environment (build 1.8.0_232-b09)
OpenJDK 64-Bit Server VM (build 25.232-b09, mixed mode)

インストール

$ pacman -Ss plantuml
community/plantuml 1.2019.13-1
    Component that allows to quickly write uml diagrams
community/plantuml-ascii-math 20171116-2
    Plantuml language extension to allow use AsciiMath or JLaTeXMath notation
$ sudo pacman -S plantuml
$ which plantuml
/usr/bin/plantuml

インストール終わり.
もう少し詳しく見てみると

$ file $(which plantuml)
/usr/bin/plantuml: POSIX shell script, ASCII text executable
$ cat $(which plantuml)
#!/bin/sh
exec /usr/bin/java -jar '/usr/share/java/plantuml/plantuml.jar' "$@"

$ plantuml test.txtすると引数をそのまま渡すだけのシンプルなシェルスクリプトのようだ.
plantuml -tpngとかつけることで出力フォーマットを指定可能.
現在サポートしているやつは以下の通り.

$ plantuml -h
(...snip...)
    -tpng               To generate images using PNG format (default)
    -tsvg               To generate images using SVG format
    -teps               To generate images using EPS format
    -tpdf               To generate images using PDF format
    -tvdx               To generate images using VDX format
    -txmi               To generate XMI file for class diagram
    -tscxml             To generate SCXML file for state diagram
    -thtml              To generate HTML file for class diagram
    -ttxt               To generate images with ASCII art
    -tutxt              To generate images with ASCII art using Unicode characters
    -tlatex             To generate images using LaTeX/Tikz format
    -tlatex:nopreamble  To generate images using LaTeX/Tikz format without preamble
    -o[utput] "dir"     To generate images in the specified directory
(...snip...)

.eps対応なのは有り難い.

hello world

簡単なhello.txtを作成して試す.

@startuml
Bob->Alice : hello world
@enduml

$ plantuml hello.txt && eog hello.png
eog hello.png部分は画像を表示するコマンド.好きな方法でよい.

f:id:poppycompass:20200131055103p:plain
plantuml(hello world)
後は好きな図を書くだけ.

文法

詳細は公式が詳しい.
配置図を見てどの要素で何が作られるのかを知ればあとはつなげていく作業.

ちょびっと自動化

makeを使って開発を少し楽する.
-guiオプションが動けばいいのかもしれないが,うまく起動しなかった.
公式サイトのトップ下にあるリアルタイムジェネレータも便利.

# Makefile
SRC = hello.txt
PICTURE = hello.png

all:
        make build
        make view

build: ${SRC}
        plantuml ${SRC} -o ${PICTURE}

view: ${SRC}
        eog -w ${PICTURE} &

今後は$ makeするだけでよい.

最終的に作りたかったもの

docker-composeを動かして複数コンテナに対する操作概要を描きたかった.

f:id:poppycompass:20200131055402p:plain
plantuml(installer)
ライブラリやツールを使っていることを示すときはそれらのロゴ画像を貼り付けた方がわかりやすいが,そこまで力を入れる必要がなければこれで十分と判断.
参考にコードは以下のとおり.

@startuml
:developer:
package "Host OS" {
  rectangle "terminal"
  [developer] --> [terminal]
  rectangle "main"
  [terminal]  --> [main]:$ docker-compose up -d --build
  rectangle "Docker"
  [main] --> [Docker]
}
node "container1" {
  rectangle "Ubuntu 16.04 64bit"
}
node "container2" {
  rectangle "Ubuntu 16.04 32bit"
}
node "container3" {
  rectangle "CentOS 7 64bit"
}
[Docker] --> container1:./install.sh
[Docker] --> container2:./install.sh
[Docker] --> container3:./install.sh
database "Log" {
}
container1 --> Log
container2 --> Log
container3 --> Log
skinparam database {
  backgroundColor LightYellow
}
@enduml

おまけ(失敗例)

先程の図の各コンテナからのログをユーザに返すといった意味合いで作ろうとしたらこうなった.

f:id:poppycompass:20200131055837p:plain
pluntuml(installer2)
スタートがどこかわかりにくい.
上から下へ,左から右へというルールの下で作っていくのが良さそう.
全自動だから線の引き方は経験で向き合う必要がありそうだ.

所感

概要図作成において深く考えなくても図が作れるから便利.
ただし,各要素は何を表現するために使うかのルールは持っておかないと自分も相手も混乱しそうだ.
しばらくはこれを使うことにする.

Arch Linuxの有線LANが繋がらなかったしょうもない話

あるときしばらく有線LANを使わずにWifiでのみネットを使っていた.
久しぶりに有線LANを差したら問題なさそうに見えるのにネットにつながらない(IPが取れてない).
結論を言えばdhcpが動いていなかった.
そんなしょうもない話を戒めに書き残す.
さっさと解決したい人は$ sudo systemctl status dhcpcdする.
原因が同じならそれが答えだ.

環境

いつもの儀式.

$ uname -a
Linux poppycompass 5.4.8-arch1-1 #1 SMP PREEMPT Sat, 04 Jan 2020 23:46:18 +0000 x86_64 GNU/Linux

ただのArch Linux 64bitのようだ.

現象

  1. PCにLANケーブルをつなぐ
  2. LANケーブルコネクタのリンクランプ点灯(緑・オレンジ共)
  3. ネットにつながらない

調査

まずはdmesg

$ dmesg
[26247.169774] e1000e: enp0s31f6 NIC Link is Up 1000 Mbps Full Duplex, Flow Control: Rx/Tx

インターフェイスは立ち上がっている.

$ ip a
2: enp0s31f6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether <MAC addr> brd ff:ff:ff:ff:ff:ff
    inet6 <addr>/64 scope link
       valid_lft forever preferred_lft forever

おや,IPv4のアドレスがない.
世界はIPv6に移行したんだっけ?(してない)

思いつく原因と対応(復旧しなかったやつ)

  • そういえば最近アップデートしたからまさか?
    $ sudo pacman -Syu
  • 原因不明にはこれが効く!
    $ sudo reboot
  • つないだまましばらくしたらつながるだろう
    → つないだまま放置

解決編

うーん,原因はIPv4が取れていないことにありそうだ. Googlearch linux cannot get ip addrで検索してそれっぽいのがあった.
最初の方の"systemctl status dhcpcd", the service is active (running).を見て一応確認

$ systemctl status dhcpcd
Unit enable.service could not be found.
● dhcpcd.service - dhcpcd on all interfaces
     Loaded: loaded (/usr/lib/systemd/system/dhcpcd.service; disabled; vendor preset: disabled)
     Active: inactive (dead)

犯人はこいつのようだ.

$ sudo systemctl start dhcpcd
● dhcpcd.service - dhcpcd on all interfaces
     Loaded: loaded (/usr/lib/systemd/system/dhcpcd.service; disabled; vendor preset: disabled)
     Active: active (running) since Wed 2020-01-08 22:16:53 JST; 3s ago
   ...(snip)...

これで解決.

所感

最初の調査段階でIPが取れていない時点でネットワーク周りのソフトが全部動いているかどうかを確認するべきだったと反省.

参考

https://bbs.archlinux.org/viewtopic.php?id=214111