Go AWS SDKを使用してEC2インスタンスにコマンドを実行する

## はじめに
GoのAWS SDKを使用してAWSのEC2インスタンスにコマンドを送信し、その結果を取得してみました。sshで接続してからのコマンド実行ではなく、AWS Systems Manager (SSM)の機能を使用してEC2インスタンスにコマンドを実行させました。

### 前提条件

* **SSM Agentのインストール**: EC2インスタンスにSSM Agentがインストールされている必要があります。これにより、EC2のインスタンスがAWS Systems Managerと通信できるようになります。多くのAWS提供のAMIにはSSM Agentが事前にインストールされています。また、今回やりませんでしたが、手動でインストールすることもできます

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/manually-install-ssm-agent-linux.html

*  **権限の設定**: コマンドを実行するためには、IAMロールやポリシーを設定し、必要な権限を付与する必要があります。例えば、EC2インスタンスには、AmazonSSMManagedInstanceCoreポリシーをアタッチしたIAMロールを設定することでSSMからEC2に対してコマンド実行できるようになります

## EC2インスタンスをSystems Managerのマネージドインスタンスとして登録（権限の設定）
今回は、Systems Managerの「高速セットアップ」機能を使用し、Default Host Management Configuration (DHMC)を設定しました。これにより、対象のEC2インスタンスに自動的にAmazonSSMManagedEC2InstanceDefaultPolicyが適用され、Systems Managerのマネージドインスタンスとして登録されます。この場合でも、SSMからEC2に対してコマンドを実行できるようになります。

以下は、DHMC設定時のキャプチャです。
* Host Managementの設定
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1284235/dbbf4e65-311d-2d7a-d916-af8521a6eca5.png)

* 登録した結果を確認
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1284235/a6288f3d-7161-48c8-ba69-5972fe9ceb0a.png)


## 作成したコードについて
GoのAWS SDKを使用してEC2インスタンスでコマンドを実行するコードです。SSMの機能を使用してEC2に対してコマンドを実行します。

### コマンド実行

```go
package main

import (
	"fmt"
	"log"
 	"time"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/ssm"
)

// 中間のコードを省略

	// 1.AWSセッションの作成
	sess, err := session.NewSessionWithOptions(session.Options{
		Config: aws.Config{
			Region: aws.String("ap-northeast-1"), // 適切なリージョンを指定
		},
		Profile: "default", // 適切なプロファイルを指定
	})
	if err != nil {
		log.Fatalf("Failed to create session: %v", err)
	}

    // 2. SSMクライアントの作成  
	ssmClient := ssm.New(sess)

    // 3. コマンドの設定と送信
	instanceId := "your-instance-id" // EC2インスタンスIDを指定
	cmdstring := "echo Hello, World!" // 実行したいコマンドを指定
	output, err := ssmClient.SendCommand(&ssm.SendCommandInput{
		InstanceIds:  []*string{aws.String(instanceId)},
		DocumentName: aws.String("AWS-RunShellScript"),
		Parameters: map[string][]*string{
			"commands": {
				aws.String(cmdstring),
			},
		},
	})

	if err != nil {
		log.Fatalf("Failed to send command: %v", err)
		return
	}

	// 4. コマンド実行結果の取得
	commandID := output.Command.CommandId
 
	time.Sleep(5 * time.Second) // 5秒待つ
    result, err := ssmClient.GetCommandInvocation(&ssm.GetCommandInvocationInput{
		CommandId:  commandID,
		InstanceId: aws.String(instanceId),
	})
	if err != nil {
		log.Fatalf("Failed to get command result: %v", err)
		return
	}

	// コマンドの出力を表示
	fmt.Printf("Command output: %s\n", *result.StandardOutputContent)

```
#### コードの説明
次の処理をおこなっています。
1. AWSセッションの作成
AWS SDKのセッションを作成します。リージョンとプロファイルを指定して、AWSリソースにアクセスするための設定を行います

2. SSMクライアントの作成 
作成したセッションを使用して、SSMクライアントを初期化しています。

3. コマンドの設定と送信
SendCommandメソッドを使用して、指定したEC2のインスタンスにコマンドを送信しています。AWS-RunShellScriptドキュメントを使用して、シェルコマンドを実行するように設定しています

4. コマンド実行結果の取得
ssmClient.SendCommandが実行完了までまたないため、5秒間WaitしたのGetCommandInvocationメソッドを使用して、実行したコマンドの結果を取得させていますが、実際には、result.statusがsuccessになるまでループさせて待つ方法がいいと思います。

## おわりに
GoのAWS SDKを使用してEC2インスタンスにコマンドを実行し、その結果を取得しました。セキュリティと権限の管理には気を付ける必要がありますが、プログラムによる制御でリモートからコマンドを実行できるため、運用や管理が自動化、効率化できると思います。