cloud functionsからGCEへsshコマンドを送信する方法 | Google Cloud Platform

今回はサウナじゃないです。。すみません。。

GCP環境で開発を行い始めてから1年が経過しました。
今ではAWSよりもGCPの方がわかるようになり、うれしいのやら悲しいのやら。。

 

さて今回は、

“みんな大好きcloud functions”から”まだまだ現役GCE”へsshコマンドを飛ばす方法

 

ことの始まりは、
SSL証明書を定期更新したかったのですが、インスタンスグループ化しているので、
グループの更新のcolud functionsの間にSSL証明書更新のコマンドを叩かなくてはいけない。というシーン

 

cloud functionsでsshでコマンドなんて叩けるの??

 

調べてみたところ、
OS Loginをを行えばできる方法は見つかったけど、
OS Loginを許可してしまうと他のサービス(今回だとcloud build)からsshできなくなることがある。

うーん、どうしたものか、、

つくるかー となって作りました。

 

 

【環境】

 

  • cloud functions 第一世代
  • python 3.8ランタイム

pythonはできる限り3.8ランタイムを使用してください。

※3.10を使用したらsubprocessでsshが使えなくて困りました。
※これは3.10からsshのパスが通ってないから起こるらしいです。

 

 

【実装】

 

全体の流れはこんな感じ

  1. GCE側にsshパブリックキーを配置
  2. シークレットマネージャーに秘密鍵を登録
  3. cloud functionsの構成を組む
    1. シークレットマネージャーの値を呼び出す
    2. その他の設定
  4. コーディング

箇条書きだととっても簡単ですね✨

 

1.GCE側にsshパブリックキーを配置

何かしらの方法でsshのパブリックキー、プライベートキーを発行してください。

今回は≪PuTTYgenで両キーを発行→プライベートキーをOpenSSH形式に変換≫で行いました。

わからない人はPuTTYgenで両キーを発行とプライベートキーをOpenSSH形式に変換を読んでください。

このパブリックキーをGCEに配置します。

インスタンスの編集画面を開いて
「セキュリティとアクセス > SSH 認証鍵」

ここに貼り付け。

 

もちろんGCE全体のメタデータのSSH認証鍵に登録しても大丈夫です。

 

 

 

2.シークレットマネージャーに秘密鍵を登録

Secret Managerを開いてシークレットを作成ボタン
適当に名前を付けてシークレットを作成します。
赤枠部分からSSHプライベートキーをアップロードしてください。

 

 

 

3.cloud functionsの構成を組む

関数の作成を押していただいて、
適当に名前を付けていただいて、
第1世代を選択。

 

ランタイムは

ランタイムサービスアカウントに、
Secret Manager のシークレット アクセサーの権限をもつアカウントをセットしてください。

これが無いとシークレットマネージャーの値が読み込めません。

 

ちなみにこちらタイムアウトも伸ばしています。
sshがつながりにくい場合を考えてます。

 

 

 

次に、シークレットとイメージのリポジトリを開いて、
先に登録したシークレットを呼び出す。

今回は環境変数「SSH_PRIVATE_KEY」に値を載せています。

 

 

 

 

あとやらなくてはいけないのは接続の設定。
GCEと同じVPCネットワークにあるアクセスコネクタを設定してあげてください。

 

 

4.コーディング

基本的には先のOS loginのコードを参考にしています。

まずrequirements.txt

# package>=version
google-api-python-client==2.47.0
google-auth==2.6.2
google-auth-httplib2==0.1.0
google-cloud-compute==1.6.1
requests==2.27.1

 

次にmain.py(エントリーポイントはmain)

import argparse
import subprocess
import time
from typing import List, Optional, Tuple
import uuid
import requests
import os


SSH_KEY_PATH = '/tmp/dev-user'



def execute(
    cmd: List[str],
    cwd: Optional[str] = None,
    capture_output: Optional[bool] = False,
    env: Optional[dict] = None,
    raise_errors: Optional[bool] = True
) -> Tuple[int, str]:
    print(f'Executing command: {cmd}')
    process = subprocess.run(
        cmd,
        cwd=cwd,
        stdout=subprocess.PIPE if capture_output else subprocess.DEVNULL,
        stderr=subprocess.STDOUT,
        text=True,
        env=env,
        check=raise_errors
    )
    output = process.stdout
    returncode = process.returncode


    if returncode:
        print(f'Command returned error status {returncode}')
        if capture_output:
            print(f"With output: {output}")


    return returncode, output




def run_ssh(cmd: str, private_key_file: str, username: str, hostname: str) -> str:
    ssh_command = [
        'ssh',
        '-i', private_key_file,
        '-o', 'StrictHostKeyChecking=no',
        '-o', 'UserKnownHostsFile=/dev/null',
        f'{username}@{hostname}',
        cmd,
    ]
    print(f"Executing ssh command: {' '.join(ssh_command)}")
    tries = 0
    while tries < 3:
        try:
            ssh = subprocess.run(
                ssh_command,
                shell=False,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                check=True,
                env={'SSH_AUTH_SOCK': ''},
            )
        except subprocess.CalledProcessError as e:
            print('ERROR_output:', e.output)
            print('ERROR_stderr:', e.stderr)
            print('ERROR_stdout:', e.stdout)
            time.sleep(5)
            tries += 1
        else:
            print(ssh.stdout)
            return ssh.stdout



def main(
    request,
) -> str:
    cmd = 'uname -a'
    project = 'xxx-yyyy' #プロジェクト名
    instance = 'instance-centos' # インスタンス名
    zone = 'asia-northeast1-b' # zone名
    username = 'dev-user' # ssh接続ユーザー名
    hostname = f'{instance}.{zone}.c.{project}.internal'
    private_key_file = SSH_KEY_PATH


    # プライベートキーを指定パスに配置
    with open(private_key_file, 'w') as f:
        f.write(os.environ.get('SSH_PRIVATE_KEY', 'Specified environment variable is not set.'))


    # 権限を変更
    os.chmod(private_key_file, 0o400)
    
    # ssh実行
    result = run_ssh(cmd, private_key_file, username, hostname)
    print('result:',result)


    # プライベートキーを削除
    execute(['rm', private_key_file])
    return 'OK'

 

【実行etc..】

実行するとこんな感じ

 

ssh初回接続のWarningが出ているけどこれで動く。

これが行えばcloud pub/subとschedulerを使用して定期実行動かせます。
何かGCE外で定期実行させたいコマンドがある場合はご利用ください。


--------------------------
システム開発のご要望・ご相談はこちらから

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です