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


Google Compute Engineとは?
Google Compute Engine(GCE)は、Googleが提供するIaaSサービスで、仮想マシンをクラウド上で実行できるサービスです。様々なOSやマシンタイプを選択でき、必要に応じてスケールアップ・ダウンが可能な柔軟なサービスとなっています。
今回は、このGCE上にDebian VMを構築し、SSHですぐに接続できるようにするシェル関数を作成します。この作成したシェル関数が常用の関数やスニペット的な存在となり、開発環境やテスト環境を素早く立ち上げることができるようになります。
前提条件
このシェル関数を使用するには、以下の準備が必要です。
- Google Cloud SDKがインストールされていること。
- CLI上で、
gcloud auth loginでGCPアカウントにログインしていること。 - 対象のプロジェクトが作成済みであること。
- Compute Engine APIが有効化されていること。
また、Google Chrome以外のブラウザでもChrome Remote Desktopを使える・・・?かもしれないので、その場合はChrome Remote Desktopの拡張機能を入れれば使える・・・?(Chrome以外で使ったことがないのでちょっと分かりません。)
とりあえずまあ、構築の方をやっていきましょう。
シェル関数の実装
主要な関数の紹介
今回のDebian VM構築の流れは以下のような感じです。以下のシェル関数で行っていきます。
- VMインスタンスを作成して設定する -
create_gce_instance_and_configure - ルーターとNATを作成する -
create_gce_router_and_nat - ファイアウォール設定とSSH接続の設定を行う -
setup_gce_firewall_and_ssh - SSH鍵をコピーする -
copy_gce_ssh_key - インスタンスにSSH接続する -
connect_gce_instance
それでは、各関数の詳細を見ていきましょう。
1. create_gce_instance_and_configure
この関数は、GCEインスタンスを作成し、メタデータとスタートアップスクリプトを設定します。
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インスタンスを作成します:
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関数でスタートアップスクリプトを登録します。
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引数として指定できます。
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アドレスを持たないインスタンスからインターネットにアクセスできるようになります。
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を作成します。
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接続します。
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 接続が正常に完了しました。"}以下のコマンドは、以下の処理を行います。
setup_gce_firewall_and_ssh instance-test- IAP TCP転送用のSSHファイアウォールルールを作成
- VPC内SSH用のファイアウォールルールを作成
- SSH秘密鍵のコピーを実行
- インスタンスへのSSH接続を実行
ファイアウォールルール名に日付を追加することで、同じ名前のルールが既に存在する場合でも、新しいルールを作成できます。
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インスタンスにコピーします。
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関数内で既に実行されているため、通常は個別に実行する必要はありませんが、必要に応じて使用できます。
copy_gce_ssh_key instance-test5. connect_gce_instance
この関数は、GCEインスタンスにSSH接続します。
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関数内で既に実行されているため、通常は個別に実行する必要はありませんが、既存のインスタンスに接続する場合などに使用できます。
以下のコマンドは、以下の処理を行います。
connect_gce_instance instance-testIAP(Identity-Aware Proxy)の設定により、Google Cloud Identity-Aware Proxy(IAP)を使用してSSH接続します。IAPを使用すると、外部IPアドレスを持たないインスタンスにも安全にアクセスできます。
gcloud compute scp "$ssh_key_path" "${instance_name}:/tmp" \ --zone="$zone" \ --tunnel-through-iapそんなこんなで、先程適当に長く入力したキーフレーズを再び入力すれば、GCEインスタンスにSSH接続が出来ます。このリンクからChromeリモートデスクトップにアクセスして、その「SSH経由でセットアップする」からパソコンを設定します。その過程でPINを設定すれば、その後はリモデでアクセス出来ます。

実際の使用方法
これらの関数を組み合わせて、SSH接続できるDebian VMを構築する手順は以下の通りです。
# 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 <<EOFUsage: ${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 <<EOFUsage: ${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 <<EOFUsage: ${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 <<EOFwhile true; do dbus-update-activation-environment --systemd DBUS_SESSION_BUS_ADDRESS DISPLAY XAUTHORITY 2> /dev/null && breakdone
export GTK_IM_MODULE=fcitxexport QT_IM_MODULE=fcitxexport XMODIFIERS="@im=fcitx"if [ $SHLVL = 1 ] ; then (fcitx5 --disable=wayland -d --verbose '*'=0 &) xset -r 49 > /dev/null 2>&1fiEOF ) 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=yesINSTALL_CINNAMON=yesINSTALL_CHROME=yesINSTALL_FULL_DESKTOP=yes
# Any additional packages that should be installed on startup can be added hereEXTRA_PACKAGES="less bzip2 zip unzip tasksel wget"
set_env_var_from_custom_metadata VSC_PROFILE_URLset_env_var_from_custom_metadata DISCORD_WEBHOOK_URLset_env_var_from_custom_metadata GCE_ICON_URLsend_discord_notification "VMのカスタムメタデータを環境変数に反映したよ!"
send_discord_notification "VMのスタートアップスクリプトを実行するよ!"
apt-get update
# Install X Windows desktop systemif ! is_installed chrome-remote-desktop; then # install_desktop_env_with_xfce install_desktop_env_with_lxqtfi
# install_desktop_env_with_xfce# install_desktop_env_with_lxqtsend_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_localeecho "[INFO] Install japanese locale completed"send_discord_notification "VMのロケールの設定が完了したよ!"
setup_japanese_timezoneecho "[INFO] Setup timezone completed"send_discord_notification "VMのタイムゾーンの設定が完了したよ!"
install_japanese_input_methodecho "[INFO] Pre-installation of japanese input methods completed"send_discord_notification "VMのIMEの設定が完了したよ!"
setup_dev_resourcesecho "[INFO] Setup development resources completed"send_discord_notification "VMの開発リソースの設定が完了したよ!"
setup_vscodeecho "[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引数として指定できます。
create_gce_instance_and_configure instance-test us-central1-a e2-medium /path/to/custom-metadata.yml3. Chrome Remote Desktopを設定する。
connect_gce_instance関数は、SSH接続時にChrome Remote Desktopの設定を促すメッセージを表示します。これを利用して、GUIが必要な作業も行えるようになります。
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内のデスクトップから実行しないと設定できない事柄があったりもします。例えば、日本語キーボード配列に変更することです。
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など、用途に応じてカスタマイズしてみてください。
おしまい


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