Ubuntu 18.04でVMWare Workstation Player 14が動作するまで

はじめに

Ubuntu 18.04でVMWare Workstation Player 14を動かすのに苦労したので、記録を残しておく。

環境

$ uname -srvm
Linux 4.15.0-20-generic #21-Ubuntu SMP Tue Apr 24 06:16:15 UTC 2018 x86_64

初回の起動に失敗 (1)

インストール完了後の初回起動時、GCCをインストールしていなかったので、以下のようにGCCのパスを入力するように求められた。

f:id:redj:20180522031302p:plain

一旦終了し、以下のようにGCCをインストールして解消。

$ sudo apt install gcc build-essential linux-headers-$(uname -r)

初回の起動に失敗 (2)

再度VMWare Workstation Playerを起動したが、以下のようにモジュールのビルドで失敗して起動できなかった。

f:id:redj:20180522005445p:plain

続いて以下のようにポップアップが出た。

f:id:redj:20180522005441p:plain

ポップアップのメッセージに従ってログを確認。

$ sudo cat /tmp/vmware-root/vmware-14987.log
(中略)
2018-05-18T22:20:59.882+09:00| vthread-1| I125: Extracting the vmmon source from "/usr/lib/vmware/modules/source/vmmon.tar".
2018-05-18T22:20:59.917+09:00| vthread-1| I125: Successfully extracted the vmmon source.
2018-05-18T22:20:59.917+09:00| vthread-1| I125: Building module with command "/usr/bin/make -j8 -C /tmp/modconfig-hTdKsp/vmmon-only auto-build HEADER_DIR=/lib/modules/4.15.0-20-generic/build/include CC=/usr/bin/gcc IS_GCC_3=no"
2018-05-18T22:21:06.590+09:00| vthread-1| W115: Failed to build vmmon.  Failed to execute the build command.

vmmon.tarに含まれるソースのビルドに失敗している。 コンパイルエラーの詳細が出力されていないので、ログに記録されている一時ディレクトリを作成し、一度そこで自力でビルドしてエラーを確認してみる。 なお、いちいちsudoが面倒なので、以降ではsudo su -を実行済みの状態でコマンドを打っている。

$ mkdir /tmp/vmware-root/modconfig-hTdKsp
$ cd /tmp/vmware-root/modconfig-hTdKsp
$ tar xf /usr/lib/vmware/modules/source/vmmon.tar
$ /usr/bin/make -j8 -C /tmp/modconfig-hTdKsp/vmmon-only auto-build HEADER_DIR=/lib/modules/4.15.0-20-generic/build/include CC=/usr/bin/gcc IS_GCC_3=no
(中略)
/tmp/modconfig-hTdKsp/vmmon-only/linux/driver.c: In function ‘LinuxDriverInitTSCkHz’:
/tmp/modconfig-hTdKsp/vmmon-only/linux/driver.c:254:22: error: assignment from incompatible pointer type [-Werror=incompatible-pointer-types]
    tscTimer.function = LinuxDriverEstimateTSCkHzDeferred;
                      ^
/tmp/modconfig-hTdKsp/vmmon-only/linux/driver.c:256:12: error: ‘struct timer_list’ has no member named ‘data’
    tscTimer.data     = 0;
            ^
(後略)

「ポインタの型が違う」とか「メンバ変数がない」とか言っているので、どうもカーネルの構造体に非互換があるように思える。 ググった結果、以下にSUSEのエンジニア(?)によるパッチがあることが判明。

README.mdおよびINSTALLを参照してパッチを適用すると、VMWare Workstation Playerの起動に成功した。

VMのリストが表示できない

VMWare Workstation Playerの起動に成功したものの、「Create a New Virtual Machine」しようが「Open a Virtual Machine」しようが、VMがリストに表示されず、VMの起動ができない。

f:id:redj:20180519014917p:plain

ググってみると、衝撃の回答を発見。

This only happens if you have deactivated 'Record file & application usage' in Ubuntu System Settings 'All Settings / Security & Privacy / Files & Applications'.

If you activate 'Record file & application usage' your list of VMs will not be lost anymore, as the file 'recently-used.xbel' will no longer be emptied. I'm actually very happy to have discovered this, because before I had to manually restore the list of VMs dozens of time.

回答はUbuntu 14.04に対するものなので設定箇所は若干違うが、今でもこの挙動らしい。 「設定」→「プライバシー」→「使用と履歴」の「最近使用したファイル」をオンにして解決。 信じられない仕様だな。。。

f:id:redj:20180519015505p:plain

AnsibleでCentOS 7のfirewalldを無効化

firewalldを切り忘れていたために、httpアクセスが通らずに3時間ほど悩んだ。 しかもIEが「DNSエラー」と表示してくれたおかげで、名前解決を疑って遠回りしてしまった。 自分への戒めのために、Ansibleでfirewalldを無効化する方法を残しておく。

- name: firewalldの無効化
  systemd:
    name: firewalld
    state: stopped
    enabled: false

ディレクトリを再帰的に辿ってファイルのダイジェストを生成

ディレクトリのコピーが正常終了したかどうかを、ファイルのダイジェストを元に確認したかった。 コマンドの組み立てに、いくらかの調べものと試行錯誤をしたので、メモしておく、 結果的に、コマンドは以下に落ち着いた。

$ find -type f -print0 | sort -z | xargs -0 sha1sum

ハッシュ関数

ハッシュ関数はsha1sumを利用した。 md5sumでもsha256sumでもよかったが、sha256sumだと実行時間が体感でsha1sumの倍ぐらいあったということで、sha1sumにしておいた。

執筆時点で、ファイル破損の検出にどのハッシュ関数を使うのがスタンダードなのかはよく分からない。 参考までに、CentOSダウンロードサイトではsha1sumとsha256sumの計算結果がアップロードされていた。 また、Ubuntuダウンロードサイトではmd5sum、sha1sum、sha256sumの計算結果がアップロードされていた。

セパレータ

xargsで空白および記号を含むファイル名を正しく取り扱えるように、データ(ここではファイルのパス)の区切りを空白や改行ではなく、NUL (\0)にする。 パイプによる連結を想定しているコマンドは、セパレータをNULにするオプションを備えているものが多いように思う。 ここで利用しているコマンドとオプションは以下。

コマンド オプション
find -print0
sort -z
xargs -0

AnsibleでのSELinuxの無効化とリブート

はじめに

CentOS7でSELinuxを無効化するPlaybookを書くのが一苦労だったので、試行錯誤の結果を記録しておく。

試した環境

Playbookを実行する側

CentOS 7.4をyum updateで最新化して、ansibleをインストールした環境。

$ uname -srvm
Linux 3.10.0-693.21.1.el7.x86_64 #1 SMP Wed Mar 7 19:03:37 UTC 2018 x86_64

$ cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)

$ ansible-playbook --version | grep -E '^ansible-playbook'
ansible-playbook 2.4.2.0

$ python --version
Python 2.7.5

Playbookを実行される側

CentOS 7.4をインストールした直後の環境。

$ uname -srvm
Linux 3.10.0-693.el7.x86_64 #1 SMP Tue Aug 22 21:09:27 UTC 2017 x86_64

$ cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)

$ python --version
Python 2.7.5

Playbook

ここではselinux-disablerというロールを作成した。 tasks/main.ymlは以下の通り。

- name: (1) SELinux用のPythonモジュールをインストール
  yum: name=libselinux-python state=installed

- name: (2) SELinuxの無効化
  selinux: state=disabled
  register: selinux

- name: (3) SSHのポート番号の取得
  set_fact:
    ssh_port: "{{ hostvars[inventory_hostname].ansible_port if 'ansible_port' in hostvars[inventory_hostname] else 22 }}"
  when: selinux.reboot_required

- name: (4) マシンのリブート
  shell: "sleep 2 && reboot"
  async: 1
  poll: 0
  when: selinux.reboot_required

- name: (5) マシンの停止を待ち合わせ
  local_action: wait_for host={{ inventory_hostname }} port={{ ssh_port }} state=stopped
  when: selinux.reboot_required

- name: (6) マシンの起動を待ち合わせ
  local_action: wait_for host={{ inventory_hostname }} port={{ ssh_port }} state=started
  when: selinux.reboot_required

Playbookの解説

(1) SELinux用のPythonモジュールをインストール

- name: (1) SELinux用のPythonモジュールをインストール
  yum: name=libselinux-python state=installed

モジュールselinuxを利用するためには、事前にlibselinux-pythonのインストールが必要。

(2) SELinuxの無効化

- name: (2) SELinuxの無効化
  selinux: state=disabled
  register: selinux

モジュールselinuxを利用してSELinuxを無効化。 以前は冪等性が正しく実装されていなかったのか、ググるとコマンドgetenforceの実行結果を元にモジュールselinuxを実行するかどうかを判断してるサンプルが多数引っかかる。 例えば以下のような感じ。

- name: SELinuxの状態を取得
  shell: getenforce
  changed_when: False
  register: selinux_state

- name: SELinuxの無効化
  selinux: state=disabled
  when: selinux_state.stdout != 'Disabled'

ドキュメント的には、Ansible 2.4からモジュールselinuxのreturn valuesについての説明が加わっているので、このあたりで整備されたと思われる。

なお、リブートが必要かどうかはモジュールselinuxの戻り値のreboot_requiredを調べると分かる。

(3) SSHのポート番号の取得

- name: (3) SSHのポート番号の取得
  set_fact:
    ssh_port: "{{ hostvars[inventory_hostname].ansible_port if 'ansible_port' in hostvars[inventory_hostname] else 22 }}"
  when: selinux.reboot_required

ここは、単に好き嫌いの問題。 ハードコーディングが気にならないのであれば、わざわざSSHのポート番号をひねくり出さなくても、wait_forでの待ち合わせで以下のように「port=22」にしておけばよい。

- name: マシンの起動を待ち合わせ
  local_action: wait_for host={{ inventory_hostname }} port=22 state=started

ネット上にあるサンプルでは、上記のようにwait_forの待ち合わせでSSHのポート22がハードコーディングされているものが殆どだった。 ハードコーディングが嫌いなので標準の変数がないか探したところ、ansible_portがそれに該当することが分かった。 さらに、ansible_portはhostvars経由で参照するようなので、以下のように試してみた。

- name: マシンの起動を待ち合わせ
  local_action: wait_for host={{ inventory_hostname }} port={{ hostvars[inventory_hostname].ansible_port }} state=started

しかし、この変数は以下のようにインベントリで明示的に指定している場合は使えるのだが、そうでない場合は「未定義」となり、参照箇所でエラーが発生する。

[server]
192.168.8.3 ansible_port=22

なんで適切なデフォルト値で変数を準備してくれないんだよ、と思って調べてみると、以下の報告があった。

結論は、バグではなく仕様と言い切っている。

but if you want to ensure a variable value is always available for use in templates and stuff, you'll need to set it yourself- the connection plugins don't have a way to smuggle out the value they used.

文意を崩さないように心がけつつ意訳。

もし、テンプレートでの利用か何かの都合でansible_portの値が必ず存在していてほしいのであれば、自分自身でansible_portの値を明示的に設定しておく必要がある。 なぜならば、現在のところコネクションプラグインが利用している内部的なデフォルト値を外に出す方法は準備されていないからだ。

つまり、SSHプラグインでは22がデフォルトで、WinRMのプラグインでは5986がデフォルトだが、これらのプラグインがデフォルト値をansible_portなどに書き出す仕組みは現状実装されていないので諦めてくれ、ということらしい。 仕方がないので、以下のようにした。

  set_fact:
    ssh_port: "{{ ansible_portが存在するならその値、存在しなければ22 }}"

最終的に、Jinja2 (Python)で書くと以下のようになった。

  set_fact:
    ssh_port: "{{ hostvars[inventory_hostname].ansible_port if 'ansible_port' in hostvars[inventory_hostname] else 22 }}"

(4) マシンのリブート

- name: (4) マシンのリブート
  shell: "sleep 2 && reboot"
  async: 1
  poll: 0
  when: selinux.reboot_required

コマンドrebootを利用してリブートする。

ただし、単純にrebootを実行するとSSHの切断が検知されてplaybookの実行に失敗する。 そのためasyncを利用して非同期実行を行う。 その際には(結果確認の?)ポーリングをオフにするために、pollに0を指定しておく。

また、rebootと同時に実行しているsleepは何らかのタイミング調整だが、詳細は把握しきれていない。 asyncの値とsleepの引数の値には何か制約がありそうな気がする。 例えば、async < sleepでなければならない、等。 試しにsleepを削除したところ、SSHの切断が検知されてplaybookの実行が失敗した。

詳細はそのうち整理しよう。

(5) マシンの停止を待ち合わせ

- name: (5) マシンの停止を待ち合わせ
  local_action: wait_for host={{ inventory_hostname }} port={{ ssh_port }} state=stopped
  when: selinux.reboot_required

SSHのポートがクローズされることで、マシンが停止したと判断している。 ググって見つかるリブートのサンプルには、起動の待ち合わせを遅延実行することで、停止の待ち合わせを省略しているものもある。 例えば、以下のように。

- name: マシンのリブート
  shell: "sleep 2 && reboot"
  async: 1
  poll: 0

- name: マシンの起動を待ち合わせ (delay=30により、30秒後から待ち合わせ開始)
  local_action: wait_for host={{ inventory_hostname }} port=22 delay=30 state=started

停止の待ち合わせが本当に必要かどうかはよく分からないが、delayの値をうまく決められないので、今回は丁寧に停止の待ち合わせをしておくことにする。

(6) マシンの起動を待ち合わせ

- name: (6) マシンの起動を待ち合わせ
  local_action: wait_for host={{ inventory_hostname }} port={{ ssh_port }} state=started
  when: selinux.reboot_required

SSHのポートがオープンされることで、マシンが起動したと判断している。

実行結果 - SELinuxが有効の場合

以下の(2)で、モジュールselinuxSELinuxを無効化している。 ただしその反映にはリブートが必要なので、後続の(3)〜(6)のリブート処理を実行している。

PLAY [server] ******************************************************************

TASK [Gathering Facts] *********************************************************
ok: [192.168.8.3]

TASK [selinux-disabler : (1) SELinux用のPythonモジュールをインストール] **********************
ok: [192.168.8.3]

TASK [selinux-disabler : (2) SELinuxの無効化] **************************************
 [WARNING]: SELinux state change will take effect next reboot
changed: [192.168.8.3]

TASK [selinux-disabler : (3) SSHのポート番号の取得] *************************************
ok: [192.168.8.3]

TASK [selinux-disabler : (4) マシンのリブート] *****************************************
changed: [192.168.8.3]

TASK [selinux-disabler : (5) マシンの停止を待ち合わせ] *************************************
ok: [192.168.8.3 -> localhost]

TASK [selinux-disabler : (6) マシンの起動を待ち合わせ] *************************************
ok: [192.168.8.3 -> localhost]

PLAY RECAP *********************************************************************
192.168.8.3                : ok=7    changed=2    unreachable=0  failed=0

実行結果 - SELinuxが無効の場合

以下の(2)で、モジュールselinuxの冪等性の検査が正しく行われて、結果がokになっている。 またリブートは不要なので、後続の(3)〜(6)のリブート処理をスキップしている。

PLAY [server] ******************************************************************

TASK [Gathering Facts] *********************************************************
ok: [192.168.8.3]

TASK [selinux-disabler : (1) SELinux用のPythonモジュールをインストール] **********************
ok: [192.168.8.3]

TASK [selinux-disabler : (2) SELinuxの無効化] **************************************
ok: [192.168.8.3]

TASK [selinux-disabler : (3) SSHのポート番号の取得] *************************************
skipping: [192.168.8.3]

TASK [selinux-disabler : (4) マシンのリブート] *****************************************
skipping: [192.168.8.3]

TASK [selinux-disabler : (5) マシンの停止を待ち合わせ] *************************************
skipping: [192.168.8.3]

TASK [selinux-disabler : (6) マシンの起動を待ち合わせ] *************************************
skipping: [192.168.8.3]

PLAY RECAP *********************************************************************
192.168.8.3                : ok=3    changed=0    unreachable=0  failed=0

その他

SELinuxを無効化した後にすぐにリブートするかどうかは要件次第なので、場合によってはハンドラにリブート処理を書いたほうがよいと思われる。

SSHの情報源

日々の作業に役立てるために、SSHの情報源を適時まとめていく予定。

Windowsでのスクリーンショット

コンテキストメニュースクリーンショットなどは[Alt]+[Print Screen]ではうまく取れないので、ツールを探してみた。 Windows 7から、標準で「Snipping Tool」というツールがある模様。

参考

PowerShellの情報源