啊!SSH 連不進 GCP 的 VM!

迫野紗那
11 min readDec 14, 2022

今天突然發現我無法連上 GCP Compute Engine 的 VM ,讓我們看看這問題要如何解決。

不太重要的前情提要

Visual Studio Code 有一個很方便的功能,使用 Remote Development Extension 就能夠很簡單地把遠端環境變得像在本地一樣,用 Visual Studio Code 的各種功能,甚至它還會在需要時自動幫你設定 port forwarding ,有興趣可以看一下官方的介紹連結。因為我的桌機平常是 Windows ,平常突然要弄一些東西懶得換用筆電或是切去 Linux ,這個遠端功能讓我能在 Windows 上很方便地開發,不用去搞讓人很頭痛的 Windows 開發環境。

啊,以上這些跟今天的事件完全沒有關係……我只是想推薦大家好東西而已。

總之,我有一個開發測試用的 Ubuntu Linux VM instance ,平常沒事掛著跑一些小程式。今天下午打開 Visual Studio Code 想要連上去遠端開發時,忽然發現遇到錯誤。

錯誤訊息的內容在應該算是最常見的 SSH 錯誤訊息之一:

Permission denied (publickey)

這表示我們在 SSH 想要連進 VM instance 時,使用的 key 被拒絕。比較奇怪的是,從上次連進去到現在為止,我沒有更動過本地或遠端任何與 SSH 相關的設定,本地 key pair 的最後編輯時間也停留在當初產生的日期,沒有被誤編輯過。

思考問題所在

OK,身為一名有素養的 programmer ,遇到問題第一件事當然是……先重開 VM 。很可惜的是,重開之後問題依然存在。

為了排除問題,首先得要先確認那些最基本的地方。GCP Console 當中,Compute Engine 裡的 VM instance 名稱點進去,下方資料中會有一個欄位是 SSH Keys ,這邊就是當初設定連入 VM instance 時使用哪個 SSH key 的地方。正常情況來說,它的值應該要完全等於我的 gcp_key.pub ,而不是發現它被存在或是被改成別的值。

嗯,它的確還在,並且與 public key 一模一樣。這樣接下來還能嘗試什麼呢?

如果能夠連進 VM instance 裡面,就可以看一下 system log ,看看 sshd 有沒有什麼錯誤訊息,以及確認 authorized_keys 檔案的內容是否符合預期。不過目前連不進去的情況下,實在沒有什麼好的方法 debug 。

於是接下來我嘗試用其他方式 SSH 進去,包括在 GCP console 用網頁 SSH ,還有用 gcloud compute ssh 的 CLI command 連線,但都同樣地無法連線。

$ gcloud compute ssh USERNAME@VM_NAME
Updating project ssh metadata...
......................................................Updated [https://www.googleapis.com/compute/v1/projects/projectnameo].
..done.
Waiting for SSH key to propagate.
Server refused our key
FATAL ERROR: No supported authentication methods available (server sent: publickey)

這下麻煩了。

在文件裡游泳

在稍微搜尋一下之後,我找到了 GCP 的 SSH 連線問題疑難排解文件

首先,GCP 提到了在啟用 OS login 的狀況下,會無法使用 SSH 連線。如果是有啟用 OS login 的 VM instance ,那麼 metadata 當中會有 enable-oslogin 這個 key 。

不過我的印象是我從沒做過這個設定,在 metadata 中也的確找不到這個 key ,所以並不是這個原因造成的。

一項一項檢查下來,並沒有看到什麼像是原因的項目。在讀到「可能是因為 disk full」那項時,我一開始很快地跳過,因為我覺得我在上面跑的程式應該沒有任何一個會把磁碟塞滿,當然事實證明,用「我覺得」來下定論不是一個好習慣……

總之,我在繞了一圈之後又重新回到這個項目。文件上提到可以用 serial console 來 debug 。GCP 的 VM instance 會有四個 serial port ,其中系統相關的 I/O 主要使用 port 1 ,也就是說我們可以透過 VM 的 serial port 1 來讀到一些系統相關的 log 。指令相當簡單:

$ gcloud compute instances get-serial-port-output VM_NAME --port 1

就能夠得到一長串的系統 log 了。很快地,兇手就現身了:

Dec 14 06:09:51 playground google_guest_agent[548]: ERROR non_windows_accounts.go:143 gpasswd: /etc/group.2056: No space left on device

所以確實是 disk full (即使我一開始覺得根本不可能),那麼接下來就是要處理這個問題了。

處理磁碟空間不足的問題

處理這個問題有兩個方式,一個是最普遍的直接 resize disk ,另一個方法則是如果已經知道 instance 上面哪些檔案佔了過多的空間,可以寫一個 startup script 讓 VM 在開機時去執行 script 進行必要的清理,執行完再把 script 移除。

因為這裡我並不知道到底造成磁碟空間不足的主因是什麼,因此我打算先 resize disk ,可以正常登入之後再來看要做什麼處理。

先用 list 確認一下 disk name:

$ gcloud compute disks list
NAME LOCATION LOCATION_SCOPE SIZE_GB TYPE STATUS
DISK_NAME asia-east1-b zone 10 pd-balanced READY

下指令 resize disk ,從 10 GB resize 到 20 GB :

$ gcloud compute disks resize DISK_NAME --size 20
This command increases disk size. This change is not reversible.
For more information, see:
https://cloud.google.com/sdk/gcloud/reference/compute/disks/resize

Do you want to continue (Y/n)?

然後重開 VM instance 。好,問題解決……

不要瞎掰好嗎

並沒有!!!

從 serial console 的 log 可以看到同樣的錯誤依然在發生,disk full 並沒有解決。

為什麼呢?GCP 的文件上面明明有寫如果是使用官方的 image ,VM 應該會在下完 resize 指令並重開之後自動進行 disk partition resize 。看了一下 log ,感覺應該是因為磁碟空間不足,導致開機過程在啟動一些 service 的時候就發生錯誤,可能連帶地導致後面 partition resize 指令沒有被呼叫或是沒被成功執行。

我嘗試使用 GCP 文件推薦的 startup script 的方式去指定刪除一部分檔案,想要在開機時清出一些空間,但 startup script 竟然也沒有被執行。startup script 的執行是由 google-startup-scripts.service 控制的,在載入前面其他 service 時就已經因為 disk full 而發生錯誤了,所以無論重開幾次,startup script 都沒有被成功執行。(這部份我沒花時間深追,如果有講錯還請高手不吝指點。)

愚公移山法

事已至此,只好使用暴力手法將原本的磁碟先清出一些空間。

具體的步驟如下:

  1. 從原本 VM1 的磁碟 disk1 建立一個 snapshot
  2. 用這個 snapshot 建立第二個磁碟 disk2
  3. 建立一個新的 VM instance ,我們稱它為 VM2
  4. 將 disk2 連接到 VM2 上(作為 data disk)並 mount 起來
  5. 在 VM2 上操作,將 disk2 裡面比較大的檔案移除
  6. 將 disk1 從 VM1 移除、disk2 從 VM2 移除
  7. 把 disk2 設定為 VM1 的新開機磁碟
  8. 開啟 VM1 測試 SSH 連線,確認 partition resize 完成
  9. 刪除不需要再使用的 disk1 和 VM2

以下附上使用的指令,我知道你們最愛看這個了,因為 GCP Console 的介面真的有夠慢又難用。

1. 從原本 VM1 的磁碟 disk1 建立一個 snapshot

$ gcloud compute snapshots create SNAPSHOT_NAME \
--source-disk DISK1_NAME \
--source-disk-zone SOURCE_DISK_ZONE

2. 用這個 snapshot 建立第二個磁碟 disk2

$ gcloud compute disks create DISK2_NAME \
--size=DISK_SIZE \
--source-snapshot=SNAPSHOT_NAME \

這邊的 DISK_SIZE 是以 GB 為單位,例如要 30 GB 就寫--size=30

3. 建立一個新的 VM instance ,我們稱它為 VM2

$ gcloud compute instances create VM2_NAME \
--image-family=ubuntu-2004-lts \
--image-project=ubuntu-os-cloud
--machine-type=e2-small

4. 將 disk2 連接到 VM2 上(作為 data disk)並 mount 起來

$ gcloud compute instances attach-disk VM2_NAME \
--disk DISK2_NAME

5. 在 VM2 上操作,將 disk2 裡面比較大的檔案移除

$ gcloud compute ssh VM2_NAME

(VM2)$ sudo mkdir -p /mnt/disk2
(VM2)$ lsblk # 確認 partition name
(VM2)$ sudo mount /dev/<partition-name> /mnt/disk2
(VM2)$ sudo rm -rf /mint/disk2/<要移除的檔案或資料夾>

6. 將 disk1 從 VM1 移除、disk2 從 VM2 移除

$ gcloud compute instances stop VM1_NAME # boot disk 移除前必須先停掉 instance
$ gcloud compute instances detach-disk VM1_NAME \
--disk=DISK1_NAME
$ gcloud compute instances detach-disk VM2_NAME \
--disk=DISK2_NAME

這邊要注意的是,如果沒有先 unmount disk 就直接將其 detach ,會有 disk I/O 被中斷、潛在的 data corruption 可能性。不過我目前處理的 instance 是不太需要考慮這樣的狀況。

7. 把 disk2 設定為 VM1 的新開機磁碟

$ gcloud compute instances attach-disk VM1_NAME \
--disk=DISK2_NAME \
--boot

8. 開啟 VM1 測試 SSH 連線,確認 partition resize 完成

$ gcloud compute instances start VM1_NAME
$ gcloud compute instances ssh VM1_NAME

(VM1)$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/root 29G 8.4G 21G 29% /
tmpfs 2.0G 0 2.0G 0% /dev/shm
tmpfs 785M 964K 784M 1% /run
tmpfs 5.0M 0 5.0M 0% /run/lock
/dev/sda15 105M 5.3M 100M 5% /boot/efi
tmpfs 393M 4.0K 393M 1% /run/user/1003
tmpfs 393M 4.0K 393M 1% /run/user/1002

9. 刪除不需要再使用的 disk1 和 VM2

$ gcloud compute instances delete VM2_NAME --quiet
$ gcloud compute disks delete DISK1_NAME --queit

以上。

終於能夠連上 VM instance 了!

--

--

迫野紗那

只存在二次元的女高中生,興趣使然而寫文。