OSSの運用とライセンスについての情報源

はじめに

OSS運用のスタッフ的視点での情報減を少しずつまとめていく。 あとは、ライセンスやリーガルなことも。

とりあえずこれ

License

Contributor Agreements

wmctrlを利用したウィンドウのリサイズ

はじめに

とあるドキュメントの作成の都合上、Linuxでウィンドウのスクリーンショットを取る必要に迫られた。 ドキュメントのレイアウトの崩れを防ぐために、ウィンドウのサイズは統一しておきたかった。 そのためウィンドウを思い通りにリサイズできるコマンドを探してみた。 どうもwmctrlを使えばよいらしい。

使い方

まずウィンドウのリストを表示する。

$ wmctrl -l
0x0140000a  0 devpc デスクトップ
0x02800001  0 devpc Google - Google Chrome
0x02200003  0 devpc Mozilla Firefox
0x03a00006  0 devpc redj@devpc: ~
0x03c00001  0 devpc Untitled-1 - Visual Studio Code

表示される行のフォーマットは以下。

[ウィンドウID] [デスクトップ番号] [Xクライアントのホスト名] [ウィンドウタイトル]

例えば、座標(100, 100)を起点に、幅1280、高さ800でリサイズする場合は以下のようにする。 ここでは、対象のウィンドウはウィンドウタイトルで指定する。オプション-eのフォーマットはgravity,起点x,起点y,幅,高さで、gravityには通常は0を指定しておく。

wmctrl -r "Google - Google Chrome" -e 0,100,100,1280,800

また、ウィンドウIDを指定してリサイズする場合は、上記のコマンドラインのオプションに-iを加えて、以下のようにする。

wmctrl -i -r 0x02800001 -e 0,100,100,1280,800

参考

static int window_move_resize (Display *disp, Window win, char *arg) {
    // (略)

    if (wm_supports(disp, "_NET_MOVERESIZE_WINDOW")){
        return client_msg(disp, win, "_NET_MOVERESIZE_WINDOW", 
            grflags, (unsigned long)x, (unsigned long)y, (unsigned long)w, (unsigned long)h);
    }

    // 略

Jinja2のhello, world!

はじめに

JInja2のドキュメントの最初のサンプルコードが分かりにくくて、しばらくぶりだと色々調べ直しになる。。。 なので、最小限のコードを備忘録として残しておく。

環境

Ubuntu 18.04LTS

Jinja2のインストール

# pip3 install jinja2

hello, world!

以下の内容でgreeting.pyを作成する。

import jinja2

# create Jinja2 Environment
env = jinja2.Environment(loader = jinja2.FileSystemLoader('./'))

# load template
template = env.get_template('template.txt.j2')

# rendering
context = { 'name': 'world' }
result = template.render(context)

# print "hello, world!"
print(result)

以下の内容でtemplate.txt.j2を作成する。

hello, {{ name }}!

以下のように実行する。

$ python3 greeting.py 
hello, world!

参考

Pythonでリストからディクショナリを生成する

Pythonでキーとバリューが交互に並んだリストからディクショナリを生成するには、以下のようにする。

x = ['key1', 'value1', 'key2', 'value2']
y = dict(zip(x[0::2], x[1::2]))

まず、ステップ数を指定したスライスを利用して、x[0::2]でキーのリストを、x[1::2]でバリューのリストを生成する。 次に、zip()を利用してタプル(キー, バリュー)を要素に持つイテレータを生成する。 そして、そのイテレータを引数にしてdict()を呼び出し、ディクショナリを生成する。

ただし、キーの数とバリューの数が不揃いだった場合、zip()だと短い方に切り詰められるので、エラーハンドリングをしたいならばitertools.zip_longest()を活用する必要があるようだ。

参考

DockerコンテナでNFSのボリュームを利用する

はじめに

DockerコンテナでのNFSのボリュームを利用する手順を説明する。 ここでは、NFSサーバの情報は以下とする。

項目
IPアドレス 192.168.8.1
NFSのバージョン 4
公開ディレクト /shared

また、公開ディレクトリには、動作確認用にhello,world!という内容が書かれたファイルgreeting.txtを配置しておくことにする。

(1) ボリュームの作成

コンテナの起動に先立ち、「ボリューム」を作成しておく。

ボリュームとは、統一的なインタフェースで管理できるように、ストレージを抽象化したものである。 ボリュームの作成時に「ドライバ」を指定することで、様々なファイルシステムのボリュームを作成することができる。 Dockerがデフォルトで提供するlocalドライバは、実質的な処理をmountに委譲しているため、これを使えばNFSマウントは簡単に実現できる。

NFSのボリュームを作成するには、以下のコマンドを実行する。

$ docker volume create --driver local --opt type=nfs --opt o=addr=192.168.8.1,rw,nfsvers=4 --opt device=:/shared nfs-volume

上記のコマンドラインを見れば分かるように、オプション--optで指定できるドライバへのオプションはmountと同じである。

作成されたボリュームの情報を確認するには、以下のコマンドを実行する。

$ docker volume inspect nfs-volume
[
    {
        "CreatedAt": "2019-04-11T00:45:17+09:00",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/nfs-volume/_data",
        "Name": "nfs-volume",
        "Options": {
            "device": ":/shared",
            "o": "addr=192.168.8.1,rw,nfsvers=4",
            "type": "nfs"
        },
        "Scope": "local"
    }
]

(2) ボリュームのコンテナへのアタッチ

ボリュームをコンテナにアタッチする方法は、オプション--mountを利用する方法と、オプション--volumeを利用する方法の、2つがある。

オプション--mountを利用する場合は、以下のようになる。

$ docker run --mount source=nfs-volume,target=/shared -it ubuntu

また、オプション--volumeを利用する場合は、以下のようになる。

$ docker run --volume nfs-volume:/shared -it ubuntu

動作確認

ボリュームがアタッチされているかどうか確認する。 先の(2)の手順でubuntuを実行しているので、まずはコンテナの中からマウントの状態を確認する。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay          98G   17G   77G  18% /
tmpfs            64M     0   64M   0% /dev
tmpfs           2.0G     0  2.0G   0% /sys/fs/cgroup
:/shared         98G   17G   77G  18% /shared
/dev/sda2        98G   17G   77G  18% /etc/hosts
shm              64M     0   64M   0% /dev/shm
tmpfs           2.0G     0  2.0G   0% /proc/asound
tmpfs           2.0G     0  2.0G   0% /proc/acpi
tmpfs           2.0G     0  2.0G   0% /proc/scsi
tmpfs           2.0G     0  2.0G   0% /sys/firmware

次に、同様にコンテナの中から動作確認用に作成しておいた/shared/greeting.txtの内容を表示できるか確認する。

$ cat /shared/greeting.txt
hello,world!

最後に、コンテナの外側からコンテナの情報を確認する。 オプション--mountを利用した場合は、以下のような結果となる。

$ docker inspect c60aa2108cc5
(略)
        "Mounts": [
            {
                "Type": "volume",
                "Name": "nfs-volume",
                "Source": "/var/lib/docker/volumes/nfs-volume/_data",
                "Destination": "/shared",
                "Driver": "local",
                "Mode": "z",
                "RW": true,
                "Propagation": ""
            }
        ],
(略)

また、オプション--volumeを利用した場合は、以下のような結果となる。

$ docker inspect 98bfa1f78037
(略)
            "Mounts": [
                {
                    "Type": "volume",
                    "Source": "nfs-volume",
                    "Target": "/shared"
                }
            ],
(略)

補足: オプション--mount--volumeの違い

大雑把に言えば、アタッチしようとしているボリュームが存在しなかった時の挙動が違う。 オプション--volumeは古くからあるオプションであり、互換性を考慮すると、もはや挙動を変えることができないらしい。 そこで、オプション--volumeを残しつつも、オプション--mountを新設したとのこと。 そのため、今後は--mountを利用したほうが良い。

以降の説明では、NFSのボリュームについてのみ言及する。

オプション--mountの場合

オプション--mountの値には、ボリュームの作成に必要な各種情報を付け加えることができる。 ボリュームnfs-volumeがない状態で以下のようにコンテナを起動すると、NFSのボリュームが自動的に作成されて、それがコンテナにアタッチされる。

$ docker run --mount 'type=volume,source=nfs-volume,target=/shared,volume-driver=local,volume-opt=type=nfs,volume-opt=device=:/shared,"volume-opt=o=addr=192.168.8.1,rw,nfsvers=4"' -it ubuntu

作成されたボリュームの情報を表示すると、手順(1)で作成したものと同じであることが確認できる。

$ docker volume inspect nfs-volume
[
    {
        "CreatedAt": "2019-04-11T00:39:55+09:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/nfs-volume/_data",
        "Name": "nfs-volume",
        "Options": {
            "device": ":/shared",
            "o": "addr=192.168.8.1,rw,nfsvers=4",
            "type": "nfs"
        },
        "Scope": "local"
    }
]

オプション--volumeの場合

一方で、オプション--volumeにはボリュームを作成するための詳細情報を引き渡す手段がない。 ボリュームnfs-volumeがない状態で以下のようにコンテナを起動すると、NFSではなく「普通の」ホスト側のファイルシステムのボリュームが作成されて、それがコンテナにアタッチされる。

$ docker run --volume nfs-volume:/shared -it ubuntu

以下のように、コンテナの中からマウントの状況を確認すると、/sharedはマウントされているものの、NFSではないことが分かる。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay          98G   17G   77G  18% /
tmpfs            64M     0   64M   0% /dev
tmpfs           2.0G     0  2.0G   0% /sys/fs/cgroup
/dev/sda2        98G   17G   77G  18% /shared
shm              64M     0   64M   0% /dev/shm
tmpfs           2.0G     0  2.0G   0% /proc/asound
tmpfs           2.0G     0  2.0G   0% /proc/acpi
tmpfs           2.0G     0  2.0G   0% /proc/scsi
tmpfs           2.0G     0  2.0G   0% /sys/firmware

そのため、コンテナの中には動作確認用のファイル/shared/greeting.txtは存在しない。

$ cat /shared/greeting.txt
cat: /shared/greeting.txt: No such file or directory

コンテナの外側から、以下のように作成されたボリュームの情報を確認すると、当然だがNFSの設定はなされていない。

$ docker volume inspect nfs-volume
[
    {
        "CreatedAt": "2019-04-14T23:36:16+09:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/nfs-volume/_data",
        "Name": "nfs-volume",
        "Options": null,
        "Scope": "local"
    }
]

参考

NFS Version 4のサーバとクライアントの構築手順

はじめに

CI環境を構築するにあたって、成果物を共有するための共有ストレージが必要になった。 いろいろ検討したが、ESXiのデータストアとして利用できるという点から、NFS Version 4を採用することにした。 ネット上の情報は古いものが多くて構築に苦労したので、NFSサーバとNFSクライアントの構築手順を残しておく。 試した環境はUbuntu 18.04。

NFSサーバ

NFSサーバをインストールする。

$ sudo apt install nfs-kernel-server

公開するディレクトリを準備する。 ここでは、以下に示すディレクト/public/shared/var/ci-artifactsを公開することにする。

/
|-- public
|   `-- shared
`-- var
    `-- ci-artifacts

NFS Version 4は個別にディレクトリを公開するのではなく、特定のディレクトリをルートとして公開する思想らしい。 そのため、公開したいディレクトリをあるひとつのディレクトリのもとに集約する必要がある。 ここではバインドマウントを利用して、以下のようにディレクト/exportのもとに集約する。

/
|-- export
|   |-- ci-artifacts ←●/var/ci-artifactsをバインドマウント
|   `-- shared       ←●/public/sharedをバインドマウント
|-- public
|   `-- shared
`-- var
    `-- ci-artifacts

推測だが、シンボリックリンクだと(chrootの場合のように)管轄外には辿れないので、バインドマウントでしっかりとファイルシステムに組み込む、ということなんだと思う。 バインドマウントするためには、/etc/fstabに以下のエントリを追記する。

/export/ci-artifacts  /var/ci-artifacts  none  bind  0  0
/export/shared        /public/shared     none  bind  0  0

公開するディレクトリのマウント先を作成する。

$ sudo mkdir /export/ci-artifacts
$ sudo mkdir /export/shared

マウントする。

$ sudo mount -a

マウントできているか確認する。

$ findmnt -l | grep "/export/"
/var/ci-artifacts               /dev/sda2[/export/ci-artifacts] ext4            rw,relatime,errors=remount-ro,data=ordered
/public/shared                  /dev/sda2[/export/shared]       ext4            rw,relatime,errors=remount-ro,data=ordered

/etc/exportsを編集して、公開するディレクトリの定義を行う。

/export               192.168.8.0/24(rw,sync,all_squash,no_subtree_check,fsid=0)
/export/ci-artifacts  192.168.8.0/24(rw,sync,all_squash,no_subtree_check,nohide)
/export/shared        192.168.8.0/24(rw,sync,no_root_squash,no_subtree_check,nohide)

ここではNFS Version 4のエントリだけを定義しているが、NFS Version 3などのエントリも混在させることができる。 fsid=0とあるのが、NFS Version 4のルートのエントリとなる。 色々調べてみた結果、複数のルート(fsid=1とか?)を公開することはできないように思うが、いまいち確証は得られなかった。 また、バインドマウントの単位ごとにエントリを作成する必要があり、ここでは/export/ci-artifacts/export/sharedのエントリを作成している。

/etc/exportsの編集が完了したら、以下のコマンドを実行してディレクトリを公開する。

$ sudo exportfs -r

あるいは、NFSサーバを再起動する事で公開してもよい。

$ sudo systemctl restart nfs-server

実際に公開できたか確認する。

$ sudo exportfs
/export         192.168.8.0/24
/export/ci-artifacts
        192.168.8.0/24
/export/shared  192.168.8.0/24

NFSクライアント

まずはNFSクライアントをインストールする。

$ sudo apt install nfs-common

マウントポイントを作成する。

$ sudo mkdir /ci-artifacts
$ sudo mkdir /shared

/etc/fstabを編集する。

192.168.8.1:/ci-artifacts /ci-artifacts nfs noauto,x-systemd.automount,rw  0  0
192.168.8.1:/shared       /shared       nfs noauto,x-systemd.automount,rw  0  0

マウントオプション的には、公開ディレクトリがどのバージョンのNFSなのかは、気にしなくていいようだ。 ただし、公開ディレクトリのパスのルートはfsid=0のパスが基準になる。 そのため、今回の場合は/exportを基準にするため、/export/ci-artifacts/ci-artifactsになる。

そして、マウントする。

$ sudo mount -a

ESXiのパスワードのポリシーを変更する

はじめに

デフォルトのESXiのパスワードのポリシーはちょっときつめで、今利用している開発環境のパスワードのポリシーと合わない。 そこで、ESXiのパスワードのポリシーを少し緩めることにした。

変更方法

ファイル/etc/pam.d/passwdにパスワードのポリシーの設定があるので、これを書き換えることで変更できる。 デフォルトの/etc/pam.d/passwdの内容は以下の通り。

#%PAM-1.0

# Change only through host advanced option "Security.PasswordQualityControl".
password   requisite    /lib/security/$ISA/pam_passwdqc.so retry=3 min=disabled,disabled,disabled,7,7
password   sufficient   /lib/security/$ISA/pam_unix.so use_authtok nullok shadow sha512
password   required     /lib/security/$ISA/pam_deny.so

ここで、min=disabled,disabled,disabled,7,7の部分がパスワードの最小文字数の設定になる。 passwdqcのドキュメントによると、minのフォーマットはmin=N0,N1,N2,N3,N4であり、各項目の意味は以下の通りである。

項目 対象 意味 既定値
N0 パスワード 文字クラス1の場合の長さ disabled
N1 パスワード 文字クラス2の場合の長さ 24
N2 パスフレーズ 全体の長さ 11
N3 パスワード 文字クラス3の場合の長さ 8
N4 パスワード 文字クラス4の場合の長さ 7

文字クラスとは、パスワードが何種類の文字で構成されているかを示すもの。 passwdqcは、文字を以下の4種類に分けている。

  • 数字
  • アルファベット小文字
  • アルファベット大文字
  • その他 (記号など)

例えば、helloは文字クラス1、hello,world!は文字クラス2、Hello,World!は文字クラス3となる。

各項目の値について、0の場合は最小文字数の制限なしとなり、disabledの場合はその文字クラスは利用できなくなる。

これらを踏まえて、min=disabled,6,6,6,6と書き換えることにした。 書き換え後のファイル/etc/pam.d/passwdの内容は以下の通り。

#%PAM-1.0

# Change only through host advanced option "Security.PasswordQualityControl".
password   requisite    /lib/security/$ISA/pam_passwdqc.so retry=3 min=disabled,6,6,6,6
password   sufficient   /lib/security/$ISA/pam_unix.so use_authtok nullok shadow sha512
password   required     /lib/security/$ISA/pam_deny.so

動作確認

普段のポリシーをもとにパスワードを変更してみる。

$ passwd
Changing password for root

You can now choose the new password or passphrase.

A valid password should be a mix of upper and lower case letters,
digits, and other characters.  You can use a 6 character long
password with characters from at least 2 of these 4 classes.
An upper case letter that begins the password and a digit that
ends it do not count towards the number of character classes used.

A passphrase should be of at least 3 words, 6 to 40 characters
long, and contain enough different characters.

Alternatively, if no one else can see your terminal now, you can
pick this as your password: "sweet+Jolt$Trip".

Enter new password: ←●パスワードを入力
Re-type new password:  ←●パスワードを入力
passwd: password updated successfully

無事、パスワードの変更が完了した。

補足 - N0N4の大小関係について

結論から言うと、以下のような大小関係を満たす必要がある。

N0 >= N1 >= N2 >= N3 >= N4

つまり、利用している文字の種類が少ない場合は長さでカバーしろ、という方針だと思われる。 読み落としやすいが、passwdqcのドキュメントには、以下の記載がある。

Each subsequent number is required to be no larger than the preceding one.

最初、N2パスフレーズのパラメータなので無関係と思い、min=disabled,6,disabled,6,6としていた。 この設定でパスワードを変更しようとすると、以下のようにInvalid parameter valueとエラーになった。

$ passwd
Changing password for root
pam_passwdqc: Error parsing parameter "min=disabled,6,disabled,6,6": Invalid parameter value.
passwd: Critical error - immediate abort
passwd: 

エラーメッセージが大雑把で原因が分からないので、passwdqcのソースコードを確認したところ、以下のようであった。

static int
parse_option(passwdqc_params_t *params, char **reason, const char *option)
{
    // ...

    if ((p = skip_prefix(option, "min="))) {
        for (i = 0; i < 5; i++) {
            if (!strncmp(p, "disabled", 8)) {
                v = INT_MAX;  ←●disabledの時の値はINT_MAX
                p += 8;
            } else {
                v = strtoul(p, &e, 10);
                p = e;
            }
            if (i < 4 && *p++ != ',')
                goto parse_error;
            if (v > INT_MAX)
                goto parse_error;
            if (i && (int)v > params->qc.min[i - 1])  ←●ひとつ前と大小関係を比較
                goto parse_error;
            params->qc.min[i] = v;
        }
        if (*p)
            goto parse_error;
    } else if ((p = skip_prefix(option, "max="))) {

ソースコードによると、disabledと指定された時の実際の値はINT_MAX(=環境にもよるが2147483647とか)になる。 なので、min=disabled,6,disabled,6,6min=2147483647,6,2147483647,6,6と解釈され、大小関係を満たせずにエラーになる、という挙動みたいだ。

参考

min=N0,N1,N2,N3,N4

(min=disabled,24,11,8,7) The minimum allowed password lengths for different kinds of passwords/passphrases. The keyword disabled can be used to disallow passwords of a given kind regardless of their length. Each subsequent number is required to be no larger than the preceding one.

N0 is used for passwords consisting of characters from one character class only. The character classes are: digits, lower-case letters, upper-case letters, and other characters. There is also a special class for non-ASCII characters, which could not be classified, but are assumed to be non-digits.

N1 is used for passwords consisting of characters from two character classes that do not meet the requirements for a passphrase.

N2 is used for passphrases. Note that besides meeting this length requirement, a passphrase must also consist of a sufficient number of words (see the passphrase option below).

N3 and N4 are used for passwords consisting of characters from three and four character classes, respectively.

When calculating the number of character classes, upper-case letters used as the first character and digits used as the last character of a password are not counted.

In addition to being sufficiently long, passwords are required to contain enough different characters for the character classes and the minimum length they have been checked against.