TL;DR
ダブルクリックで指定したGCP上のwindowsインスタンスにリモートデスクトップ接続を開始するツールを作りました。
mpppk/connect-to-gce-win: Tool for connect to Windows on Google Compute Engine via Remote Desktop
クリックすると以下の処理が自動で行われます。
- 最初にwindowsインスタンスの一覧を取得し、どのインスタンスに接続するかを表示します。
- (上のgifの例では、設定ファイルで接続するインスタンスを指定しているので、チェックなしで接続を開始します)
- 指定されたインスタンスがTERMINATEDであれば、起動します。
- インスタンスのパスワードがリセットされ、新しいパスワードがクリップボードに保存されます。
- インスタンスに接続するためのRDPファイルを生成し、OSでファイルに紐づけられているRDPクライアントを自動で開きます。
- RDPクライアントがパスワードを聞いてきたら、クリップボードに保存されたパスワードをペーストするだけでログインできます。
(本当はRDPファイルにパスワードを含めることもできるっぽいのですが、めんどくさそうだったのでやってません)
背景
私はmacしか持っていないのですが、たまにwindowsでしかプレイできないゲームをやりたくなることがあります。
とはいえそんなに長時間やるわけではないので、そのためだけにそこそこハイスペックなwindows機を買うのも気が引けます。
で、よく考えると、動きの少ないカードゲーム等であれば、リモートでも十分プレイ可能では?ということで、やりたくなった時だけGCP上のwindowsインスタンスを立ち上げることにしました。
お値段も東京リージョン/8CPU/10GBメモリで$0.379/hourと、新しくWindows PCを買うことを考えれば大変お安いです。
(これは異常にCPUリソースを消費するMagic Arenaというゲームでの例なので8CPUという豪華な構成ですが、他のほとんどのゲームであればもっと安く済むと思います。n1-standard-2(2CPU/7.5GBメモリ)なら$0.1220/hですね。)
GCP上のwindowsへ接続するまでの手順
通常、GCPのwindowsを立ち上げるには以下のステップを踏みます。
(インスタンスの作成とRDPクライアントのインストールは済んでいるものとします)
- GCEのダッシュボードを開き、起動したいインスタンスの右側の「起動」を選択
- 起動したらパスワードをリセットして、新しいパスワードをコピーしておく
- ローカルにパスワードを保存しておくより、毎回リセットした方が安全なので
- 同じメニューからRDPファイルをダウンロード
- RDPクライアントで先ほどダウンロードしたRDPファイルを開く
- パスワードを聞かれたら、先ほどコピーしたパスワードを入力
- ゲームが終わったらインスタンスを落とす
本当は静的IPアドレスを振っておけば同じRDPファイルを使い回せるのですが、インスタンスを落としている間もお金がかかってしまうので、毎回新しくRDPファイルをダウンロードしています。
まあこれでも十分お手軽なので、ゲームをプレイする前に毎回上記の手順を行なっていたのですが、怠惰な人間なのでもっと楽に接続したくなってきました。
golangで一連の処理を行う
GCPの起動やパスワードのリセットは全てAPI経由でも操作可能ですので、自動化できそうです。言語は何でも良いですが、今回はgolangで書いていくことにします。
パッと調べた感じ、GCEのgolang SDKは以下の2種類が使えそうです。
-
googleapis/google-cloud-go: Google Cloud Client Libraries for Go.
- google APIの定義から自動生成されたライブラリ
-
Daisy | compute-image-tools
- GCEに関するワークフローをDSLで定義できるっぽい?(今回はその機能は使ってません)
それぞれでしかできないことがあったりするので、今回は2つを併用しています。
クライアントを生成
daisyは以下のようにimportしている前提です。
import daisyCompute "github.com/GoogleCloudPlatform/compute-image-tools/daisy/compute"
ctx := context.Background()
// google apiのクライアント生成
googleDefaultClient, err := google.DefaultClient(ctx)
gceClient, err := compute.New(googleDefaultClient)
// daisyのクライアント生成
daisyClient, err := daisyCompute.NewClient(ctx)
指定したインスタンスを取得
daisyClient.GetInstance
で指定した名前のインスタンスの情報を取得できます。
instance := daisyClient.GetInstance(c.project, c.zone, instanceName)
fmt.Println(instance) // => e.g. "TERMINATED"
インスタンスの一覧を取得
gceCompute.ListInstance
で指定した名前のインスタンスの情報を取得できます。
instances, err := gceClient.ListInstances()
インスタンスを起動
daisyClient.StartInstance
で指定した名前のインスタンスを起動し、STATUSが"RUNNING"になるまでブロックします。
gceClient
でも起動はできるのですが、起動完了を知るためにGetInstanceを定期的に叩いてSTATUSをチェックしなければならないので、daisyの方が楽です。
err := daisyClient.StartInstance(instance.Name)
パスワードをリセット
パスワードのリセットは特にAPIが用意されているわけではなく、Metadata
のwindows-keys
という項目へ暗号化した新たなパスワードを追加します。
オフィシャルドキュメントで詳しい方法が解説されているので、こちらをご覧ください。
Windows パスワード生成の自動化 | Compute Engine ドキュメント | Google Cloud
RDPファイルを生成
RDPファイルは単に以下のフォーマットで書かれたテキストファイルです。
full address:s:[target_ip_address]
username:s:[your_username]
というわけで、インスタンスからIPアドレスを取得して、ファイルを生成します。
func extractNatIpFromInstance(instance *compute.Instance) (string, error) {
if len(instance.NetworkInterfaces) == 0 {
return "", errors.New("no NetworkInterfaces found")
}
accessConfigs := instance.NetworkInterfaces[0].AccessConfigs
if len(accessConfigs) == 0 {
return "", errors.New("no AccessConfigs found")
}
return accessConfigs[0].NatIP, nil
}
func generateRDPFile(filePath, ip, userName string) error {
contents := "full address:s:" + ip + "\n" + "username:s:" + userName
return ioutil.WriteFile(filePath, []byte(contents), 0644)
}
natIP, err := extractNatIpFromInstance(instance)
err = generateRDPFile("test.rdp", natIP, "your_username")
RDPファイルを開く
skratchdot/open-golangでRDPファイルを開けば、OSで紐づけられたクライアントが起動します。
err := open.Run("test.rdp")
実際の実装
これらの処理を実際に書いている部分が、connect-to-gce-win/cmd/root.goです。
実際に動作するコードを確認したい方はこちらをご覧ください。
これで、GCP上のWindowsに一瞬で接続できるようになりました。
Windows PC持ってない方はぜひご検討ください٩(●˙▿˙●)۶