VMware Workstation で Linuxの時刻を3コマンドで合わせる方法

# はじめに

検証環境や閉環境などのインターネットもNTPもない環境で仮想マシンを作成すると、ゲストOS（Linux）の時刻が9時間進んた時刻になってしまいます。
dateコマンドを使って現実世界の時計を見ながら設定したり、スクリプトを使って-9時間した時刻を設定している方もいるのではないでしょうか。

しかし、ホストOSの時刻とゲストOSの時刻はなぜずれてしまうのでしょうか。

本記事では、時刻がずれてしまう原因の簡単な解説を行い、dateコマンドを使わず、現実世界の時計も見ず、
ホストOS（Windows）の時刻（JST）を基準として時刻合わせを行う方法を紹介します。

# 環境
- ホストOS: Windows10
- VMware Workstation 16
- ゲストOS：Ubuntu 20.04

# 状況と原因

VMware Workstation で仮想マシンを作成したとき、インターネット接続もなく、ntp環境もない場合はゲストOSの時刻はいつまでもおかしな時刻を指します。
「おかしな」というのは、ホストOSであるWindowsの時刻は現実世界に一致しているのに、ゲストOSであるLinuxは、ホストOSのローカルタイム（日本時刻JST）をUTCとして扱ってしまっていることです。

下記に示すように、現実世界とWindowsが11:00 JST のとき、ゲストOSの表示時刻（JST）は9時間進んだ時刻 20:00 JST を示してしまいます。

| 測定場所 | 時刻 |
| --- | --- | 
| 現実世界（日本時刻） | 11:00 JST |
| ホストOS Windows表示時刻 | 11:00 JST |
| ゲストOS Linux表示時刻（UTC） | 11:00 UTC （おかしい）|
| ゲストOS Linux表示時刻（JST） | 20:00 JST | 

原因の解説は [仮想化雑記帳: 仮想マシンとハードウェアクロック](https://virtnote.blogspot.com/2017/08/blog-post.html) を読んでいただくとして、簡単に解説すると、

- 仮想マシンが初回に起動するときは、VMware WorkstationがホストOS（Windows）のローカルタイム（日本時刻）を仮想マシンのリアルタイムクロック（RTC）に書き込む
- RTCにタイムゾーンの概念はない
- 仮想マシン上のゲストOS（Linux）は、RTCの時刻をUTCとして扱う
- なので、Linuxが表示するローカルタイム（日本時刻）はUTCから9時間進んだ時刻になる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13722/633ed429-938b-3c77-dd67-ca61fb52ba69.png)

# 3コマンドで合わせる方法

## 結論

何も考えずに次の3コマンドを実行すればOKです。Linuxインストール時に設定したタイムゾーンには依存しません。

```sh
sudo timedatectl set-timezone Asia/Tokyo    # ホストOSのタイムゾーンに合わせる
sudo hwclock --hctosys --localtime          # ハードウェアクロック（hc）の時刻をローカルタイムとみなして、システムクロック（sys）に適用する
sudo hwclock --systohc --utc                # システムクロック（sys）の時刻を、ハードウェアクロック（hc）に適用する。その際、hcはUTCを表示させるべきものとして扱います
```

## 解説

各コマンドの実行結果を、timedatectl の結果を確認しながら進めます。

なお、作業時点でのWindowsの表示時刻は 「10:00 JST」頃となっています。

まずは、`timedatectl` コマンドで現在の状況を確認します。
RTC timeが10:00となってしまっています。
RTC timeはUTC時刻を表示していてほしいので、ここをなんとか01:00（日本時刻の9時間前）にするのが目標です。

```sh
$ timedatectl 
               Local time: Wed 2021-12-15 10:00:38 UTC
           Universal time: Wed 2021-12-15 10:00:38 UTC
                 RTC time: Wed 2021-12-15 10:00:37     # ここを01:00:37と表示されるようにしたい
                Time zone: Etc/UTC (UTC, +0000)       
System clock synchronized: no                         
              NTP service: active                     
          RTC in local TZ: no                         
```

まずは、`timedatectl set-timezone`コマンドで、LinuxのローカルタイムゾーンをJSTにします。
ここでJSTを選んだ理由は、ホストOSのタイムゾーンがJSTであることと、以降のコマンドで、RTC timeの時刻をJSTとして扱いためです。
ホストOSの時刻がESTの方は、LinuxのローカルタイムゾーンをESTにしてください。
（作業後は好きなタイムゾーンに変更できます）

```sh
$ sudo timedatectl set-timezone Asia/Tokyo
$ timedatectl 
               Local time: Wed 2021-12-15 19:04:08 JST   # ローカルタイムゾーンがJSTになった
           Universal time: Wed 2021-12-15 10:04:08 UTC
                 RTC time: Wed 2021-12-15 10:04:07    
                Time zone: Asia/Tokyo (JST, +0900)    
System clock synchronized: no                         
              NTP service: active                     
          RTC in local TZ: no                         
```

次に、`hwclock --hctosys`コマンドで、hc（ハードウェアクロック、RTC）時刻をsys（システムクロック、Linuxの時計）に適用します。
この際、`--localtime`オプションを付与することで、hcがローカルタイム（JST時刻）を表示しているものとして扱います。

```sh
$ sudo hwclock --hctosys --localtime 
$ timedatectl 
               Local time: Wed 2021-12-15 10:04:34 JST   # RTC time の時刻（JST扱い）がLocal timeに適用された
           Universal time: Wed 2021-12-15 01:04:34 UTC
                 RTC time: Wed 2021-12-15 10:04:34    
                Time zone: Asia/Tokyo (JST, +0900)    
System clock synchronized: no                         
              NTP service: active                     
          RTC in local TZ: no                         
```
最後に、`hwclock --systohc`コマンドで、sys（システムクロック、Linuxの時計）を、hc（ハードウェアクロック、RTC）に適用します。
この際、`--utc`オプションを付与することで、hcはUTCを表示させるべきものとして扱います。
つまり、`timedatectl`コマンドの「Universal time」がRTCに適用されます。

```sh
$ sudo hwclock --systohc --utc
$ timedatectl 
               Local time: Wed 2021-12-15 10:04:48 JST
           Universal time: Wed 2021-12-15 01:04:48 UTC
                 RTC time: Wed 2021-12-15 01:04:48        # Universal time が Local timeに適用された
                Time zone: Asia/Tokyo (JST, +0900)    
System clock synchronized: no                         
              NTP service: active                     
          RTC in local TZ: no    
```

これで仮想マシンのRTCが、期待する状態になりました。
仮想マシンを再起動しても、電源OFF/ONしても、クローンしても時刻は期待する状態を維持します。

ただし、仮想マシンをエクスポートしてova,ovf形式にし、その後インポートした場合にはまた時刻がおかしくなります。
RTCの時刻はovaファイルに保存されないためです。
（この表現は正確ではないので、解説は[仮想化雑記帳: 仮想マシンとハードウェアクロック](https://virtnote.blogspot.com/2017/08/blog-post.html) を参照ください）

## まとめ

- 仮想マシンの初回起動時はOSのローカルタイムをUTCとして扱ってしまう
- dateコマンドで時刻を修正することもできるが、RTCを正しい値に修正することで正しい状態にすることができる
- コマンドは、何も考えずに3行実行するだけでよい