【Google Compute Engine】SSH接続できるDebian VMを構築するシェル関数を作る

5442 語
27 分
【Google Compute Engine】SSH接続できるDebian VMを構築するシェル関数を作る

はじまり#

リサちゃん avatar
リサちゃん
う~ん、GUIメンドクサイ!
135ml avatar
135ml
それではCLIで完結するようにしましょう。

Google Compute Engineとは?#

Google Compute Engine(GCE)は、Googleが提供するIaaSサービスで、仮想マシンをクラウド上で実行できるサービスです。様々なOSやマシンタイプを選択でき、必要に応じてスケールアップ・ダウンが可能な柔軟なサービスとなっています。

今回は、このGCE上にDebian VMを構築し、SSHですぐに接続できるようにするシェル関数を作成します。この作成したシェル関数が常用の関数やスニペット的な存在となり、開発環境やテスト環境を素早く立ち上げることができるようになります。

前提条件#

このシェル関数を使用するには、以下の準備が必要です。

  1. Google Cloud SDKがインストールされていること。
  2. CLI上で、gcloud auth loginでGCPアカウントにログインしていること。
  3. 対象のプロジェクトが作成済みであること。
  4. Compute Engine APIが有効化されていること。

また、Google Chrome以外のブラウザでもChrome Remote Desktopを使える・・・?かもしれないので、その場合はChrome Remote Desktopの拡張機能を入れれば使える・・・?(Chrome以外で使ったことがないのでちょっと分かりません。)

とりあえずまあ、構築の方をやっていきましょう。

シェル関数の実装#

主要な関数の紹介#

今回のDebian VM構築の流れは以下のような感じです。以下のシェル関数で行っていきます。

  1. VMインスタンスを作成して設定する - create_gce_instance_and_configure
  2. ルーターとNATを作成する - create_gce_router_and_nat
  3. ファイアウォール設定とSSH接続の設定を行う - setup_gce_firewall_and_ssh
  4. SSH鍵をコピーする - copy_gce_ssh_key
  5. インスタンスにSSH接続する - connect_gce_instance

それでは、各関数の詳細を見ていきましょう。

1. create_gce_instance_and_configure#

この関数は、GCEインスタンスを作成し、メタデータとスタートアップスクリプトを設定します。

Terminal window
function create_gce_instance_and_configure() {
local FUNC_NAME="create_gce_instance_and_configure"
# --help オプションのチェック
for arg in "$@"; do
if [ "$arg" = "--help" ]; then
echo "[INFO] Usage: ${FUNC_NAME} INSTANCE_NAME [ZONE] [MACHINE_TYPE] [YAML_FILE] [STARTUP_SCRIPT_FILE]"
echo " INSTANCE_NAME : 作成するインスタンスの名前"
echo " ZONE : インスタンスを作成するゾーン (デフォルト: us-central1-a)"
echo " MACHINE_TYPE : マシンタイプ (デフォルト: e2-medium)"
echo " STARTUP_SCRIPT_FILE : スタートアップスクリプトのファイルパス (デフォルト: ./iac/gcloud/setup_scripts/startup-script.sh)"
echo " YAML_FILE : metadata設定用のYAMLファイル (デフォルト: env.yml)"
return 0
fi
done
# 引数チェック:最低1つは INSTANCE_NAME を指定する必要があります
if [ "$#" -lt 1 ]; then
echo "[ERROR] ${FUNC_NAME}: Usage: ${FUNC_NAME} INSTANCE_NAME [ZONE] [MACHINE_TYPE] [STARTUP_SCRIPT_FILE] [YAML_FILE]" >&2
return 1
fi
# パラメータの初期化(デフォルト値付き)
local INSTANCE_NAME="$1"
local ZONE="${2:-us-central1-a}"
local MACHINE_TYPE="${3:-e2-medium}"
local YAML_FILE="${4:-env.yml}"
local STARTUP_FILE="${5:-./iac/gcloud/setup_scripts/startup-script.sh}"
# 1. インスタンス作成
if ! create_gce_instance "$INSTANCE_NAME" "$ZONE" "$MACHINE_TYPE"; then
echo "[ERROR] ${funcName}: Failed to create instance '$INSTANCE_NAME'." >&2
return 1
fi
# 2. YAMLファイルからmetadataの設定
if ! set_gce_instance_metadata_from_yaml "$INSTANCE_NAME" "$ZONE" "$YAML_FILE"; then
echo "[ERROR] ${FUNC_NAME}: インスタンス '$INSTANCE_NAME' のmetadata設定に失敗しました。" >&2
return 1
fi
# 3. とスタートアップスクリプトの登録
if ! add_startup_script_to_gce_instance "$INSTANCE_NAME" "$ZONE" "$FILE_PATH"; then
echo "[ERROR] ${funcName}: Failed to add startup script to instance '$INSTANCE_NAME'." >&2
return 1
fi
echo "[INFO] ${FUNC_NAME}: インスタンス '$INSTANCE_NAME' の作成、スタートアップスクリプト登録、metadata設定が正常に完了しました。"
return 0
}

以下のコマンドは、以下のパラメータを使用してVMインスタンスを作成します:

Terminal window
create_gce_instance_and_configure instance-test us-central1-a e2-medium
  • インスタンス名: instance-test
  • ゾーン: us-central1-a
  • マシンタイプ: e2-medium

内部的には、create_gce_instance関数を呼び出してインスタンスを作成し、set_gce_instance_metadata_from_yaml関数でメタデータを設定し、add_startup_script_to_gce_instance関数でスタートアップスクリプトを登録します。

Terminal window
function set_gce_instance_metadata_from_yaml() {
local vm_name="$1"
local zone="$2"
local yaml_file="${3:-env.yml}"
local fn_name="set_gce_instance_metadata_from_yaml"
# ヘルプオプション
if [[ "$1" == "--help" ]]; then
echo "Usage: set_instance_metadata_from_yaml <VM_NAME> <ZONE> [YAML_FILE]"
echo "Example: set_instance_metadata_from_yaml my-vm us-central1-a env.yml"
echo "If YAML_FILE is not specified, 'env.yml' will be used by default."
return 0
fi
# YAMLファイルの存在チェック
if [[ ! -f "$yaml_file" ]]; then
echo "Error: YAML file '$yaml_file' not found!"
return 1
fi
# YAMLを key=value 形式に変換
local metadata_args=""
while IFS=":" read -r key value; do
# 空白を除去
key=$(echo "$key" | xargs)
value=$(echo "$value" | xargs)
# 空行やコメント行を無視
if [[ -n "$key" && -n "$value" && "$key" != \#* ]]; then
metadata_args+="$key=$value,"
fi
done < "$yaml_file"
# 最後のカンマを削除
metadata_args="${metadata_args%,}"
if [[ -z "$metadata_args" ]]; then
echo "[INFO] No valid metadata found in '$yaml_file'"
return 0
fi
# gcloud compute instances add-metadata を実行
gcloud compute instances add-metadata "$vm_name" --zone "$zone" --metadata "$metadata_args"
local ret_code=$?
if [ $ret_code -eq 0 ]; then
echo "[INFO] ${fn_name}: Env variables of instance '${vm_name}' have set successfully."
else
echo "[ERROR] ${fn_name}: Failed to set env variables for instance '${vm_name}'."
return $ret_code
fi
}
function add_startup_script_to_gce_instance() {
local funcName="add_startup_script_to_gce_instance"
local DEFAULT_ZONE="us-central1-a"
local DEFAULT_FILE_PATH="./shell/setup_scripts/startup-script.sh"
# --help が指定された場合は利用方法を表示
if [ "$1" = "--help" ]; then
echo "[INFO] ${funcName}: Usage: ${funcName} VM_NAME ZONE [FILE_PATH]"
echo "[INFO] ${funcName}: Example: ${funcName} my-vm ${DEFAULT_ZONE} ${DEFAULT_FILE_PATH}"
echo "[INFO] ${funcName}: If ZONE is not provided, default value '${DEFAULT_ZONE}' is used."
echo "[INFO] ${funcName}: If FILE_PATH is not provided, default value '${DEFAULT_FILE_PATH}' is used."
return 0
fi
# 必須パラメータの数チェック (1~3個)
if [ "$#" -lt 1 ] || [ "$#" -gt 3 ]; then
echo "[ERROR] ${funcName}: Invalid number of parameters." >&2
echo "[INFO] ${funcName}: Usage: ${funcName} VM_NAME [ZONE] [FILE_PATH]" >&2
return 1
fi
local VM_NAME="$1"
local ZONE="${2:-$DEFAULT_ZONE}"
# 3番目の引数が未指定の場合はデフォルト値を使用
local FILE_PATH="${3:-$DEFAULT_FILE_PATH}"
# gcloud コマンドの実行
if ! gcloud compute instances add-metadata "${VM_NAME}" --zone="${ZONE}" --metadata-from-file startup-script="${FILE_PATH}"; then
echo "[ERROR] ${funcName}: Failed to add metadata from file '${FILE_PATH}' to instance '${VM_NAME}' in zone '${ZONE}'." >&2
return 1
fi
echo "[INFO] ${funcName}: Successfully added metadata from file '${FILE_PATH}' to instance '${VM_NAME}' in zone '${ZONE}'."
return 0
}

VMの起動時に特定のスクリプトを実行したい場合は、カスタムのスタートアップスクリプトを作成し、create_gce_instance_and_configure関数の第5引数として指定できます。

Terminal window
create_gce_instance_and_configure instance-test us-central1-a e2-medium env.yml /path/to/custom-startup-script.sh

スタートアップスクリプトに関しては後述します!

2. create_gce_router_and_nat#

この関数は、Cloud RouterとCloud NATを作成します。これにより、外部IPアドレスを持たないインスタンスからインターネットにアクセスできるようになります。

Terminal window
function create_gce_router_and_nat() {
local FUNC_NAME="create_gce_router_and_nat"
# --help オプションのチェック
for arg in "$@"; do
if [ "$arg" = "--help" ]; then
echo "[INFO] Usage: ${FUNC_NAME} ROUTER_NAME [REGION] [NETWORK] [NAT_NAME]"
echo " ROUTER_NAME : 作成するルーターの名前"
echo " REGION : ルーターとNATを作成するリージョン (デフォルト: us-central1)"
echo " NETWORK : ルーターを作成するネットワーク (デフォルト: default)"
echo " NAT_NAME : 作成するNATの名前 (デフォルト: nat1)"
return 0
fi
done
# 引数チェック:最低1つはルーター名を指定する必要があります
if [ $# -lt 1 ]; then
echo "[ERROR] Usage: ${FUNC_NAME} ROUTER_NAME [REGION] [NETWORK] [NAT_NAME]" >&2
return 1
fi
# パラメータの初期化(デフォルト値付き)
local router_name="$1"
local region="${2:-us-central1}"
local network="${3:-default}"
local nat_name="${4:-nat1}"
# gcloud コマンドの存在確認
if ! command -v gcloud >/dev/null 2>&1; then
echo "[ERROR] Error: gcloud コマンドが見つかりません。Google Cloud SDKがインストールされているか確認してください。" >&2
return 1
fi
# ルーター作成処理
if ! gcloud compute routers create "$router_name" \\\\
--region="$region" \\\\
--network="$network"; then
echo "[ERROR] Error: ルーター '$router_name' の作成に失敗しました。" >&2
return 1
fi
# NAT作成処理
if ! gcloud compute routers nats create "$nat_name" \\\\
--router="$router_name" \\\\
--region="$region" \\\\
--auto-allocate-nat-external-ips \\\\
--nat-all-subnet-ip-ranges; then
echo "[ERROR] Error: NAT '$nat_name' の作成に失敗しました。" >&2
return 1
fi
echo "ルーター '$router_name' と NAT '$nat_name' が正常に作成されました。"
}

以下のコマンドは、以下のパラメータを使用してCloud RouterとCloud NATを作成します。

Terminal window
create_gce_router_and_nat router1 us-central1 default nat1
  • ルーター名: router1
  • リージョン: us-central1
  • ネットワーク: default
  • NAT名: nat1

これにより、外部IPアドレスを持たないインスタンスからインターネットにアクセスできるようになります。

3. setup_gce_firewall_and_ssh#

この関数は、SSH接続に必要なファイアウォールルールを設定し、SSH鍵をコピーして、インスタンスにSSH接続します。

Terminal window
function setup_gce_firewall_and_ssh() {
local FUNC_NAME="setup_gce_firewall_and_ssh"
# --help オプションのチェック
for arg in "$@"; do
if [ "$arg" = "--help" ]; then
echo "[INFO] Usage: ${FUNC_NAME} INSTANCE_NAME [ZONE] [SSH_KEY_PATH]"
echo " INSTANCE_NAME : SSH 接続対象のインスタンス名 (例: crd1)"
echo " ZONE : インスタンスのゾーン (デフォルト: us-central1-a)"
echo " SSH_KEY_PATH : SSH 秘密鍵ファイルのパス (デフォルト: \\\\$HOME/.ssh/google_compute_engine)"
return 0
fi
done
# 引数チェック:最低1つはインスタンス名を指定する必要があります
if [ $# -lt 1 ]; then
echo "[ERROR] Usage: ${FUNC_NAME} INSTANCE_NAME [ZONE] [SSH_KEY_PATH]" >&2
return 1
fi
# パラメータの初期化(デフォルト値付き)
local instance_name="$1"
local zone="${2:-us-central1-a}"
local ssh_key_path="${3:-$HOME/.ssh/google_compute_engine}"
# 現在の日付 (YYYYMMDD) をサフィックスとして生成
local today
today=$(date +%Y%m%d)
# gcloud コマンドの存在確認
if ! command -v gcloud >/dev/null 2>&1; then
echo "[ERROR] Error: gcloud コマンドが見つかりません。Google Cloud SDK がインストールされているか確認してください。" >&2
return 1
fi
echo "【STEP 1】 IAP TCP 転送用の SSH ファイアウォールルールを作成中..."
# 第一引数にルール名+実行日付を渡す
if ! create_gce_iap_ssh_firewall_rule "allow-ssh-ingress-from-iap-${today}"; then
echo "[ERROR] Error: IAP 用ファイアウォールルールの作成に失敗しました。" >&2
return 1
fi
echo "【STEP 2】 VPC 内 SSH 用のファイアウォールルールを作成中..."
if ! create_gce_ingress_ssh_firewall_rule "allow-ingress-ssh-${today}"; then
echo "[ERROR] Error: VPC 内 SSH 用ファイアウォールルールの作成に失敗しました。" >&2
return 1
fi
echo "【STEP 3】 SSH 秘密鍵のコピーを実行中..."
if ! copy_gce_ssh_key "$instance_name" "$zone"; then
echo "[ERROR] Error: インスタンス '$instance_name' への SSH 秘密鍵のコピーに失敗しました。" >&2
return 1
fi
echo "【STEP 4】 インスタンスへの SSH 接続を実行中..."
if ! connect_gce_instance "$instance_name" "$zone"; then
echo "[ERROR] Error: インスタンス '$instance_name' への SSH 接続に失敗しました。" >&2
return 1
fi
echo "[INFO] ファイアウォール設定、SSH 秘密鍵のコピー、SSH 接続が正常に完了しました。"
}

以下のコマンドは、以下の処理を行います。

Terminal window
setup_gce_firewall_and_ssh instance-test
  1. IAP TCP転送用のSSHファイアウォールルールを作成
  2. VPC内SSH用のファイアウォールルールを作成
  3. SSH秘密鍵のコピーを実行
  4. インスタンスへのSSH接続を実行

ファイアウォールルール名に日付を追加することで、同じ名前のルールが既に存在する場合でも、新しいルールを作成できます。

Terminal window
local today=$(date +%Y%m%d)
create_gce_iap_ssh_firewall_rule "allow-ssh-ingress-from-iap-${today}"

4. copy_gce_ssh_key#

この関数は、ローカルのSSH鍵をGCEインスタンスにコピーします。

Terminal window
function copy_gce_ssh_key() {
local FUNC_NAME="copy_gce_ssh_key"
# --help オプションのチェック
for arg in "$@"; do
if [ "$arg" = "--help" ]; then
echo "[INFO] Usage: ${FUNC_NAME} INSTANCE_NAME [ZONE] [SSH_KEY_PATH]"
echo " INSTANCE_NAME : SSH 秘密鍵をコピーする対象のインスタンス名"
echo " ZONE : インスタンスのゾーン (デフォルト: us-central1-a)"
echo " SSH_KEY_PATH : SSH 秘密鍵ファイルのパス (デフォルト: \\\\$HOME/.ssh/google_compute_engine)"
return 0
fi
done
# 引数チェック: インスタンス名は必須
if [ $# -lt 1 ]; then
echo "[ERROR] Usage: ${FUNC_NAME} INSTANCE_NAME [ZONE] [SSH_KEY_PATH]" >&2
return 1
fi
local instance_name="$1"
local zone="${2:-us-central1-a}"
local ssh_key_path="${3:-$HOME/.ssh/google_compute_engine}"
# gcloud コマンドの存在確認
if ! command -v gcloud >/dev/null 2>&1; then
echo "[ERROR] Error: gcloud コマンドが見つかりません。Google Cloud SDK がインストールされているか確認してください。" >&2
return 1
fi
# SSH 秘密鍵のコピー処理
if ! gcloud compute scp "$ssh_key_path" "${instance_name}:/tmp" \\\\
--zone="$zone" \\\\
--tunnel-through-iap; then
echo "Error: インスタンス '$instance_name' への SSH 秘密鍵のコピーに失敗しました。" >&2
return 1
fi
echo "[INFO] SSH 秘密鍵 '$ssh_key_path' がインスタンス '$instance_name' の /tmp に正常にコピーされました。"
}

以下のコマンドは、ローカルのSSH鍵をローカルからGCEインスタンスにコピーします。コピーする際に、キーフレーズを適当に長く入力する鍵ファイルを作成する必要がありますね。

setup_gce_firewall_and_ssh関数内で既に実行されているため、通常は個別に実行する必要はありませんが、必要に応じて使用できます。

Terminal window
copy_gce_ssh_key instance-test

5. connect_gce_instance#

この関数は、GCEインスタンスにSSH接続します。

Terminal window
function connect_gce_instance() {
local FUNC_NAME="connect_gce_instance"
# --help オプションのチェック
for arg in "$@"; do
if [ "$arg" = "--help" ]; then
echo "[INFO] Usage: ${FUNC_NAME} INSTANCE_NAME [ZONE]"
echo " INSTANCE_NAME : SSH 接続するインスタンスの名前"
echo " ZONE : インスタンスが存在するゾーン (デフォルト: us-central1-a)"
return 0
fi
done
# 引数チェック: 少なくともインスタンス名は必須
if [ $# -lt 1 ]; then
echo "[ERROR] Usage: ${FUNC_NAME} INSTANCE_NAME [ZONE]" >&2
return 1
fi
local instance_name="$1"
local zone="${2:-us-central1-a}"
# gcloud コマンドの存在確認
if ! command -v gcloud >/dev/null 2>&1; then
echo "[ERROR] Error: gcloud コマンドが見つかりません。Google Cloud SDK がインストールされているか確認してください。" >&2
return 1
fi
echo ""
echo "[INFO] **Notice**: Setup Chrome Remote Desktop on your ssh connection and [Remote Desktop Service](<https://remotedesktop.google.com/headless>) if you want."
echo ""
# インスタンスへの SSH 接続処理
if ! gcloud compute ssh "$instance_name" \\\\
--zone="$zone" \\\\
--tunnel-through-iap; then
echo "[ERROR] Error: インスタンス '$instance_name' への SSH 接続に失敗しました。" >&2
return 1
fi
echo "[INFO] インスタンス '$instance_name' への SSH 接続が正常に完了しました。"
}

このコマンドは、GCEインスタンスにSSH接続します。setup_gce_firewall_and_ssh関数内で既に実行されているため、通常は個別に実行する必要はありませんが、既存のインスタンスに接続する場合などに使用できます。

以下のコマンドは、以下の処理を行います。

Terminal window
connect_gce_instance instance-test

IAP(Identity-Aware Proxy)の設定により、Google Cloud Identity-Aware Proxy(IAP)を使用してSSH接続します。IAPを使用すると、外部IPアドレスを持たないインスタンスにも安全にアクセスできます。

Terminal window
gcloud compute scp "$ssh_key_path" "${instance_name}:/tmp" \
--zone="$zone" \
--tunnel-through-iap

そんなこんなで、先程適当に長く入力したキーフレーズを再び入力すれば、GCEインスタンスにSSH接続が出来ます。このリンクからChromeリモートデスクトップにアクセスして、その「SSH経由でセットアップする」からパソコンを設定します。その過程でPINを設定すれば、その後はリモデでアクセス出来ます。

実際の使用方法#

これらの関数を組み合わせて、SSH接続できるDebian VMを構築する手順は以下の通りです。

Terminal window
# 1. VMインスタンスを作成して設定する
create_gce_instance_and_configure instance-test us-central1-a e2-medium
# 2. ルーターとNATを作成する
create_gce_router_and_nat router1 us-central1 default nat1
# 3. ファイアウォール設定とSSH接続の設定を行う
setup_gce_firewall_and_ssh instance-test
# 4. SSH鍵をコピーする(必要に応じて)
copy_gce_ssh_key instance-test
# 5. インスタンスにSSH接続する
connect_gce_instance instance-test

その他の詳細#

1. スタートアップスクリプトをカスタマイズする。#

VMの起動時に特定のスクリプトを実行したい場合は、カスタムのスタートアップスクリプトを作成してVMに反映します。今回は、create_gce_instance_and_configure関数の第5引数として指定していたスタートアップスクリプトを、以下のような内容で反映しています。

#!/bin/bash -x
#
# Startup script to install Chrome remote desktop and a desktop environment.
#
# See environmental variables at then end of the script for configuration
#
function send_discord_notification() {
# --helpオプションの確認
if [ "$1" == "--help" ] || [ "$1" == "-h" ]; then
cat <<EOF
Usage: ${FUNCNAME[0]} <通知テキスト> <Embedのテキスト> <Embedのフッターテキスト> <EmbedのアイコンのURL> <Embedの色> [DISCORD_WEBHOOK_URL]
Parameters:
通知テキスト : 通知内容のテキスト
Embedのテキスト : Embedメッセージの内容
Embedのフッターテキスト : Embedフッターに表示するテキスト
EmbedのアイコンのURL : Embedフッターに表示するアイコンのURL
Embedの色 : Embedの色(10進数の数値または以下の文字列指定が可能)
DISCORD_WEBHOOK_URL : (任意) DiscordのWebhook URL。省略した場合は環境変数DISCORD_WEBHOOK_URLを使用します。
Color Samples:
green : 4569935 (0x45BB4F)
red : 16711680 (0xFF0000)
sky_blue : 52479 (0x00CCFF)
orange : 14177041 (0xD85311)
white : 16777215 (0xFFFFFF)
blue : 39423 (0x0099FF)
yellow : 16770560 (0xFFE600)
pink : 16711833 (0xFF0099)
purple : 10494192 (0xA020F0)
gray_blue : 9212588 (0x8C92AC)
black : 3355443 (0x333333)
Examples:
# Using environment variable DISCORD_WEBHOOK_URL (5 arguments)
${FUNCNAME[0]} "通知テキスト" "Embedのテキスト" "Embedのフッターテキスト" "https://example.com/footer_icon.png" green
# Explicitly specifying DISCORD_WEBHOOK_URL as the last argument (6 arguments)
${FUNCNAME[0]} "通知テキスト" "Embedのテキスト" "Embedのフッターテキスト" "https://example.com/footer_icon.png" red "https://discord.com/api/webhooks/your_webhook_id/your_webhook_token"
EOF
return 0
fi
local message embed_text embed_footer_text embed_icon_url embed_color webhook_url
# Embedなし
# 引数の個数チェック
if [ "$#" -eq 1 ]; then
message="$1"
webhook_url="${DISCORD_WEBHOOK_URL}"
# JSONペイロードの作成
local payload=$(cat <<EOF
{
"content": "$message"
}
EOF
)
# curlでDiscordのWebhookにPOSTリクエストを送信
curl -H "Content-Type: application/json" \
-X POST \
-d "$payload" \
"$webhook_url"
return 0
fi
# Embedあり
# 引数の個数チェック
if [ "$#" -eq 5 ]; then
message="$1"
embed_text="$2"
embed_footer_text="$3"
embed_icon_url="$4"
embed_color="$5"
webhook_url="${DISCORD_WEBHOOK_URL}"
elif [ "$#" -eq 6 ]; then
message="$1"
embed_text="$2"
embed_footer_text="$3"
embed_icon_url="$4"
embed_color="$5"
webhook_url="$6"
else
echo "Error: 引数の数が正しくありません。"
cat <<EOF
Usage: ${FUNCNAME[0]} <通知テキスト> <Embedのテキスト> <Embedのフッターテキスト> <EmbedのアイコンのURL> <Embedの色> [DISCORD_WEBHOOK_URL];
EOF
return 1
fi
# いずれかの引数が空文字だった場合のエラーハンドリング
if [ -z "$message" ] || [ -z "$embed_text" ] || [ -z "$embed_icon_url" ] || [ -z "$embed_color" ] || [ -z "$webhook_url" ]; then
echo "Error: 引数に空文字が含まれています。"
cat <<EOF
Usage: ${FUNCNAME[0]} <通知テキスト> <Embedのテキスト> <Embedのフッターテキスト> <EmbedのアイコンのURL> <Embedの色> [DISCORD_WEBHOOK_URL];
EOF
return 1
fi
# Embedの色が文字列の場合、対応する10進数の値に変換
case "$embed_color" in
green)
embed_color=4569935
;;
red)
embed_color=16711680
;;
sky_blue)
embed_color=52479
;;
orange)
embed_color=14177041
;;
white)
embed_color=16777215
;;
blue)
embed_color=39423
;;
yellow)
embed_color=16770560
;;
pink)
embed_color=16711833
;;
purple)
embed_color=10494192
;;
gray_blue)
embed_color=9212588
;;
black)
embed_color=3355443
;;
*)
# 数値が直接入力されているものとみなす
;;
esac
# JSONペイロードの作成
local payload=$(cat <<EOF
{
"content": "$message",
"embeds": [
{
"description": "$embed_text",
"color": $embed_color,
"timestamp": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
"footer": {
"text": "$embed_footer_text",
"icon_url": "$embed_icon_url"
}
}
]
}
EOF
)
# curlでDiscordのWebhookにPOSTリクエストを送信
curl -H "Content-Type: application/json" \
-X POST \
-d "$payload" \
"$webhook_url"
}
function send_discord_notification_about_gce() {
# 第一引数以降のパラメータは send_discord_notification と同じ順序
# 通知テキストに [GCE] プレフィックスを追加する例
local message="$1"
local embed_text="$2"
local embed_color="$3"
local embed_footer_text="GoogleComputeEngine"
local embed_icon_url=$GCE_ICON_URL
local webhook_url=$DISCORD_WEBHOOK_URL
# webhook_url が空でないかチェックし、あれば最後の引数として渡す
if [ -n "$webhook_url" ]; then
send_discord_notification "$message" "$embed_text" "$embed_footer_text" "$embed_icon_url" "$embed_color" "$webhook_url"
else
send_discord_notification "$message" "$embed_text" "$embed_footer_text" "$embed_icon_url" "$embed_color"
fi
}
function set_env_var_from_custom_metadata() {
local key=$1
local MY_CUSTOM_VALUE=$(curl -s -H "Metadata-Flavor: Google" \
http://metadata.google.internal/computeMetadata/v1/instance/attributes/$key)
export $key="$MY_CUSTOM_VALUE"
}
function install_chrome_remote_desktop() {
curl https://dl.google.com/linux/linux_signing_key.pub \
| sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/chrome-remote-desktop.gpg
echo "deb [arch=amd64] https://dl.google.com/linux/chrome-remote-desktop/deb stable main" \
| sudo tee /etc/apt/sources.list.d/chrome-remote-desktop.list
sudo apt-get update
sudo DEBIAN_FRONTEND=noninteractive \
apt-get install --assume-yes chrome-remote-desktop
echo "[INFO] Setup for Chrome Remote Desktop completed successfully."
}
function install_xfce_desktop_env() {
# Xfce デスクトップ環境の導入
sudo DEBIAN_FRONTEND=noninteractive \
apt install --assume-yes xfce4 desktop-base dbus-x11 xscreensaver
# Xfce デスクトップ環境をデフォルトへ設定
sudo bash -c 'echo "exec /etc/X11/Xsession /usr/bin/xfce4-session" > /etc/chrome-remote-desktop-session'
# Chrome ブラウザをインストール
curl -L -o google-chrome-stable_current_amd64.deb \
https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install --assume-yes --fix-broken ./google-chrome-stable_current_amd64.deb
# Color Manager を停止
sudo systemctl stop colord
sudo systemctl disable colord
echo "[INFO] Setup for Xfce (X Windows System Desktop Environment) completed successfully."
}
function install_desktop_env_with_xfce() {
install_chrome_remote_desktop
install_xfce_desktop_env
}
function install_lxqt_desktop_env() {
# LXQt デスクトップ環境の導入
sudo DEBIAN_FRONTEND=noninteractive \
apt install --assume-yes lxqt dbus-x11
# LXQt デスクトップ環境をデフォルトへ設定
echo "exec startlxqt" > /etc/chrome-remote-desktop-session
# Chrome ブラウザをインストール
curl -L -o google-chrome-stable_current_amd64.deb \
https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install --assume-yes --fix-broken ./google-chrome-stable_current_amd64.deb
# Color Manager を停止
sudo systemctl stop colord 2>/dev/null
sudo systemctl disable colord 2>/dev/null
echo "[INFO] Setup for LXQt (X Windows System Desktop Environment) completed successfully."
}
function install_desktop_env_with_lxqt() {
install_chrome_remote_desktop
install_lxqt_desktop_env
}
function download_and_install { # args URL FILENAME
if [[ -e "$2" ]] ; then
echo "cannot download $1 to $2 - file exists"
return 1;
fi
curl -L -o "$2" "$1" && \
apt-get install --assume-yes --fix-broken "$2" && \
rm "$2"
}
function is_installed { # args PACKAGE_NAME
dpkg-query --list "$1" | grep -q "^ii" 2>/dev/null
return $?
}
function install_japanese_locale() {
# install japanese locale
sudo apt -y install locales
sudo localectl set-locale LANG=ja_JP.UTF-8 LANGUAGE="ja_JP:ja"
source /etc/default/locale
echo $LANG
# install japanese locale for desktop
sudo apt -y install task-japanese-desktop
}
function setup_japanese_timezone() {
sudo chmod 777 /etc/timezone
sudo rm /etc/localtime
echo Asia/Tokyo > /etc/timezone
sudo chmod 644 /etc/timezone
sudo dpkg-reconfigure -f noninteractive tzdata
}
function install_japanese_input_method() {
# install input methods
sudo apt update
sudo apt install fcitx5-mozc -y
touch ~/.profile
inserting=$(cat <<EOF
while true; do
dbus-update-activation-environment --systemd DBUS_SESSION_BUS_ADDRESS DISPLAY XAUTHORITY 2> /dev/null && break
done
export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS="@im=fcitx"
if [ $SHLVL = 1 ] ; then
(fcitx5 --disable=wayland -d --verbose '*'=0 &)
xset -r 49 > /dev/null 2>&1
fi
EOF
)
echo $inserting >> ~/.profile
im-config -n fcitx5
}
function setup_dev_resources() {
# Setup development resources
# local home_dir="$DEV_HOME"
sudo apt update
sudo apt install git -y
}
function setup_vscode() {
# Install VSCode
wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > packages.microsoft.gpg
sudo install -D -o root -g root -m 644 packages.microsoft.gpg /etc/apt/keyrings/packages.microsoft.gpg
sudo sh -c 'echo "deb [arch=amd64,arm64,armhf signed-by=/etc/apt/keyrings/packages.microsoft.gpg] https://packages.microsoft.com/repos/code stable main" > /etc/apt/sources.list.d/vscode.list'
rm -f packages.microsoft.gpg
sudo apt install apt-transport-https
sudo apt update
sudo apt install code
}
# Configure the following environmental variables as required:
INSTALL_XFCE=yes
INSTALL_CINNAMON=yes
INSTALL_CHROME=yes
INSTALL_FULL_DESKTOP=yes
# Any additional packages that should be installed on startup can be added here
EXTRA_PACKAGES="less bzip2 zip unzip tasksel wget"
set_env_var_from_custom_metadata VSC_PROFILE_URL
set_env_var_from_custom_metadata DISCORD_WEBHOOK_URL
set_env_var_from_custom_metadata GCE_ICON_URL
send_discord_notification "VMのカスタムメタデータを環境変数に反映したよ!"
send_discord_notification "VMのスタートアップスクリプトを実行するよ!"
apt-get update
# Install X Windows desktop system
if ! is_installed chrome-remote-desktop; then
# install_desktop_env_with_xfce
install_desktop_env_with_lxqt
fi
# install_desktop_env_with_xfce
# install_desktop_env_with_lxqt
send_discord_notification "VMのデスクトップ環境の設定が完了したよ!"
# [[ "$INSTALL_CHROME" = "yes" ]] && ! is_installed google-chrome-stable && \
# download_and_install \
# https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \
# /tmp/google-chrome-stable_current_amd64.deb
echo "[INFO] Chrome remote desktop installation completed"
send_discord_notification "VMのChromeリモデの設定が完了したよ!"
install_japanese_locale
echo "[INFO] Install japanese locale completed"
send_discord_notification "VMのロケールの設定が完了したよ!"
setup_japanese_timezone
echo "[INFO] Setup timezone completed"
send_discord_notification "VMのタイムゾーンの設定が完了したよ!"
install_japanese_input_method
echo "[INFO] Pre-installation of japanese input methods completed"
send_discord_notification "VMのIMEの設定が完了したよ!"
setup_dev_resources
echo "[INFO] Setup development resources completed"
send_discord_notification "VMの開発リソースの設定が完了したよ!"
setup_vscode
echo "[INFO] Setup for VSCode completed"
send_discord_notification "VMのVSCodeの設定が完了したよ!"
send_discord_notification_about_gce "終わった!" "VMのスタートアップスクリプトが完了したよ!" "green"

まだ、細かいところが整備途中ですが、これでLXQtデスクトップ環境が立ち上がります。スタートアップスクリプトの実行が完了すると、自分のDiscordチャンネルにWebhookを介して通知が飛ぶようにもなっています。

2. メタデータを設定する。#

そのDiscordのWebhookのURLを設定するために、VMのメタデータを設定する必要があります。

VMのメタデータを設定したい場合は、YAMLファイルを作成し、create_gce_instance_and_configure関数の第4引数として指定できます。

Terminal window
create_gce_instance_and_configure instance-test us-central1-a e2-medium /path/to/custom-metadata.yml

3. Chrome Remote Desktopを設定する。#

connect_gce_instance関数は、SSH接続時にChrome Remote Desktopの設定を促すメッセージを表示します。これを利用して、GUIが必要な作業も行えるようになります。

Terminal window
echo "[INFO] **Notice**: Setup Chrome Remote Desktop on your ssh connection and [Remote Desktop Service](<https://remotedesktop.google.com/headless>) if you want."

4. リモデで接続した後。#

リモデしてVM内のデスクトップから実行しないと設定できない事柄があったりもします。例えば、日本語キーボード配列に変更することです。

Terminal window
function update_japanese_input_method() {
# Set keyboard layout
setxkbmap -model jp109a -layout jp
fcitx5 &
fcitx5-configtool
}

トラブルシューティング#

1. SSH接続ができない場合。#

IAPを使用したSSH接続ができない場合は、以下を確認してください。

  • ファイアウォールルールが正しく設定されているか。
  • インスタンスが実行中であるか。
  • 適切なIAM権限が付与されているか。

2. 権限エラーが発生する場合。#

必要なIAM権限が付与されているか確認してください。最低限、以下の権限が必要です。

  • compute.instances.create
  • compute.instances.get
  • compute.instances.list
  • compute.zones.get
  • compute.routers.create
  • compute.routers.update
  • compute.networks.updatePolicy

3. クォータ制限に達した場合#

GCPプロジェクトには、リージョンごとにVMインスタンスの数やCPUコアの数などのクォータ制限があります。クォータ制限に達した場合は、Google Cloudのコンソールからクォータの引き上げをリクエストしてください。

まとめ#

今回は、Google Compute Engine上にSSH接続できるDebian VMを簡単に構築するためのシェル関数を作成しました。この関数を使えば、コマンド一つでVMを作成し、SSH上でChromeリモートデスクトップのPINコードを作成すれば、すぐにSSH接続することができます。

以下の処理を紹介しました。

  • GCEインスタンスを立ち上げる。
  • メタデータを設定する。
  • スタートアップスクリプトを配置する。
  • ルーターを設定する。
  • NATを設定する。
  • ファイアウォールルールを設定する。
  • SSH 秘密鍵を設定して、GCEインスタンス内にコピーする。
  • GCEインスタンスにSSH接続する。

開発環境やテスト環境の素早い構築に役立つだけでなく、このシェル関数をベースにして、より複雑な環境構築のための関数を作成することもできます。例えば、特定のソフトウェアが自動的にインストールされるVMや、特定の設定が適用されたVMなど、用途に応じてカスタマイズしてみてください。

おしまい#

リサちゃん avatar
リサちゃん
よーし、Enterキーで立ち上がるぞぉ
135ml avatar
135ml
すぐにDebianを立ち上げられます。

以上になります!

記事を共有

この記事が役に立ったなら、ぜひ他の人と共有してください!

【Google Compute Engine】SSH接続できるDebian VMを構築するシェル関数を作る
https://endorphinbath.com/posts/gce-debian-vm-ssh-shell-function/
著者
kinkinbeer135ml
公開日
2025-03-28
ライセンス
CC BY-NC-SA 4.0
関連記事 スマート
1
【Google Cloud】GitHub Actionsで認証するためのシェル関数を作る
Code Google Cloud上のリソースを使ってGitHub ActionsでCI/CDするためにシェル関数を構築します。その関数ではサービスアカウントにWorkload Identity連携をして処理の途中に通知を行ったりもします。
2
【Cloud Scheduler】コンテナを動かすために必要なgcloudコマンドをシェル関数化する
Code Google Cloud上でデプロイされたコンテナを操作するためにシェル関数を構築します。その関数には処理が完了した時やエラー発生時に通知を行ったりヘルプ機能も実装します。
3
【Cloud Run】コンテナを稼働させるまでに必要なgcloudコマンドをシェル関数化する
Code Google Cloud上でコンテナをデプロイするためにシェル関数を構築します。その関数には処理が完了した時やエラー発生時に通知を行ったりヘルプ機能も実装します。
4
【Cloud SQL】GolangでDBインスタンスの開始停止をDiscordで通知する
Code Go言語でCloud SQLに作成したDBインスタンスを自動で開始および停止するCloud Run Functionsが実行された時に、Discord上で通知を飛ばす機能を実装する手順を紹介します。
5
【GCP】GitHub Actionsからキーなしで認証するための設定
Code Google Cloud Platformに認証するときのIAM設定をシェル上から行い、GitHub Actionsで認証を行う作業を掲載します。この手順を踏むことで、IAMを管理しやすくなるかと思います。
ランダム記事 ランダム
Profile Image of the Author
kinkinbeer135ml
SIerをやめて、プログラミングを勉強しています。※Amazonアソシエイトに参加しています。
お知らせ
私のブログへようこそ!これはサンプルのお知らせです。
音楽
カバー

音楽

再生中なし

0:00 0:00
歌詞なし
カテゴリ
タグ
サイト統計
記事
287
カテゴリー
8
タグ
93
総文字数
486,174
運用日数
0
最終活動
0 日前

目次