Amazon EC2 に OS コマンドを送る (ssm-agent無し)

Amazon EC2 の外からOSコマンドを実行させるには、AWS Systems Manager (SSM) の SendCommand を用いるのが常道だと思いますが、環境上の制約で使用できない場合もあるかと思います。そこでSSMを使用することなく、OSコマンドを実行させるシンプルな仕組みを作ってみます。
(sshでのコマンド実行は自明ですし、AWSに関係ないのでここでは触れません。)

 

Systems Manager(SSM)とSendCommandについて

先ずはSSMとSendCommandについて軽く触れます。

SSMのSendCommandとは、SSMのマネージドノードとなっているEC2に対して、EC2の外からOSコマンドを送って実行できます。
「外から送って」と書くと、EC2に外からコマンドが入ってくる形のように思えますが、実際にはEC2で稼働しているssm-agent がSSMのエンドポイントに対してポーリングしており、コマンドを取ってくる形になっていると思われます。
(そうでなければEC2へのインバウンド通信許可を必要とするはずですが、実際にはそんなことはしません。)

EC2とSSMのコマンド連携について

この構成を真似することにします。

 構成の概要

EC2とSQSのコマンド連携について

上図の構成とします。SSMの位置にSQSを配置し、そこにOSコマンドを入れる。EC2はQueueを定期的に監視するシェルスクリプトを常駐させて、キューにコマンドが入っていればそれをフェッチして実行します。要するにssm-agentの簡易版で置き換えた形だというだけです。(がっかりした人はすみませんm(_ _)m )

以下、EC2とSQSの内容について簡単に述べておきます。

SQSについて

キューは以下の構成とします。

サンプルキュー構成

標準キューであるということと、メッセージ保持を1分にしています。あまり長くするとagent役のshellの障害時などにキューにコマンドがたまり、思わぬ昔のコマンドが実行されてしまうことを防いでいます。1分以上フェッチされなければそのコマンドは実行されませんが、そこは割り切っています。また、キューポリシーは適当に設定します。

EC2について

シェルが動けば何でもいいですが、上記SQSキューに対して、sqs:ReceiveMessage および sqs:DeleteMessage の権限が必要ですので、EC2にアタッチするIAMロールに付与しておきます。

 

ポーリングスクリプトとサービス化

ポーリングスクリプト

ポーリングシェルは以下のように作成してみました。シェルの詳細についてはこの話題の主題ではありませんので細かくは触れません。

#!/bin/bash

# SQSキューURL (適宜変更)
QUEUE_URL="https://sqs.ap-northeast-1.amazonaws.com/xxxxyyyyzzzz/TestQueue"

# ログディレクトリとファイル設定
LOG_DIR="/var/log/command-execution"
LOG_FILE="$LOG_DIR/command_execution.log"

# ログディレクトリの作成
if [ ! -d "$LOG_DIR" ]; then
sudo mkdir -p "$LOG_DIR"
sudo chown ec2-user:ec2-user "$LOG_DIR"
sudo chmod 755 "$LOG_DIR"
fi

# ログファイル作成
touch "$LOG_FILE"
chmod 644 "$LOG_FILE"

echo "[$(date)] SQS polling service started." >> "$LOG_FILE"

while true; do
# SQSからメッセージ取得
RESPONSE=$(aws sqs receive-message \
--queue-url "$QUEUE_URL" \
--max-number-of-messages 10 \
--wait-time-seconds 10 \
--output json 2>/dev/null
)

# RESPONSEが空ならデフォルト値を設定
if [ -z "$RESPONSE" ]; then
MESSAGE_COUNT=0
else
MESSAGE_COUNT=$(echo "$RESPONSE" | jq '.Messages | length' 2>/dev/null)
if [ -z "$MESSAGE_COUNT" ]; then
MESSAGE_COUNT=0
fi
fi

if [ "$MESSAGE_COUNT" -gt 0 ]; then
echo "$RESPONSE" | jq -c '.Messages[]' | while IFS= read -r MESSAGE; do
# ReceiptHandleとBodyを安全に取得
RECEIPT_HANDLE=$(echo "$MESSAGE" | jq -r '.ReceiptHandle' 2>/dev/null)
COMMAND=$(echo "$MESSAGE" | jq -r '.Body | @sh' 2>/dev/null | sed "s/^'//;s/'$//")

# 無効メッセージ判定
if [ -z "$RECEIPT_HANDLE" ] || [ -z "$COMMAND" ]; then
echo "[$(date)] Warning: Invalid message detected. Skipping..." | tee -a "$LOG_FILE"
continue
fi

# コマンド実行
TIMESTAMP=$(date "+%Y-%m-%d %H:%M:%S")
echo "[$TIMESTAMP] Executing command: $COMMAND" | tee -a "$LOG_FILE"

{
echo "----- START: $COMMAND -----"
eval "$COMMAND"
EXIT_CODE=$?
echo "Exit Code: $EXIT_CODE"
echo "----- END: $COMMAND -----"
} >> "$LOG_FILE" 2>&1

# メッセージ削除
DELETE_RESPONSE=$(aws sqs delete-message --queue-url "$QUEUE_URL" --receipt-handle "$RECEIPT_HANDLE" 2>&1)
if [ $? -eq 0 ]; then
echo "[$(date)] Command executed and message deleted." | tee -a "$LOG_FILE"
else
echo "[$(date)] Error deleting message: $DELETE_RESPONSE" | tee -a "$LOG_FILE"
fi
done
fi

sleep 5
done

5秒(sleep 5)ごとに指定のキューからメッセージの取得を試みます。この値はSQSの料金に影響します。
コマンド実行後はメッセージを削除しています。
また、受け取ったコマンドと実行結果をログに出力するようにしています。オプションでこのログをCloudWatch Logs に連携してもいいでしょう。配置場所は/usr/local/bin/sqs-polling.sh とします。パーミッションは適当につけておきます。

サービス化

上記スクリプトをサービス化するため、systemdのユニットファイルを作成します。

[Unit]
Description=SQS Polling Service
After=network.target

[Service]
ExecStart=/usr/local/bin/sqs-polling.sh
Restart=no
User=ec2-user
Environment="PATH=/usr/bin:/usr/local/bin"

[Install]
WantedBy=multi-user.target

上記を/etc/systemd/system/sqs-polling.service として保存して、以下コマンドでサービス起動・確認します。active(running)を確認してください。

sudo systemctl daemon-reload
sudo systemctl start sqs-polling.service
sudo systemctl status sqs-polling.service

動作確認

ではキューにコマンドを置いてみます。簡単にSQSのマネコンから実行します。loggerコマンドで/var/log/messagesに文字列を吐き出してみます。

マネコンからのSQSへのメッセージ送信
 
/var/log/messages

Feb 28 14:25:59 ip-10-0-0-210 sqs-polling.sh[5414]: [2025-02-28 14:25:59] Executing command: logger "hello . Tech Harmony!!!!!!!!!!!!!!!!!!!!!!!!!!"
Feb 28 14:25:59 ip-10-0-0-210 ec2-user[5415]: hello . Tech Harmony!!!!!!!!!!!!!!!!!!!!!!!!!!
Feb 28 14:26:01 ip-10-0-0-210 sqs-polling.sh[5422]: [Fri Feb 28 02:26:01 PM JST 2025] Command executed and message deleted.

シェルのログファイル

[2025-02-28 14:25:59] Executing command: logger "hello . Tech Harmony!!!!!!!!!!!!!!!!!!!!!!!!!!"
----- START: logger "hello . Tech Harmony!!!!!!!!!!!!!!!!!!!!!!!!!!" -----
Exit Code: 0
----- END: logger "hello . Tech Harmony!!!!!!!!!!!!!!!!!!!!!!!!!!" -----
[Fri Feb 28 02:26:01 PM JST 2025] Command executed and message deleted.

となって動作が確認できました。

キューにコマンドを置くことができれば何でもいいので、例えばLambda関数が該当キューにOSコマンドをputすれば、Lambdaが間接的にEC2のOSコマンドを打ったことになります。

最後に

EC2から他のAWSサービスへの連携はAWS CLIなどを使えば容易なのですが、AWSサービスからEC2(の中)への連携は手段が限られているように感じましたので、少し考えて作成してみました。

要点は、EC2から外部の状態を取りに行く、という点です。ここではSQSを例にしましたが、S3などを考えても面白いでしょう。
(定期的にS3のあるオブジェクトの存在を確認し、存在すればダウンロードして(以下略))

やったことはssm-agentの超簡易版を作成したに過ぎませんが、そこまで複雑でないことはわかっていただけたと思います。
コマンドの結果をサービス側で知るにはもうちょっと考える必要はありそうです。
SSMが使えるならそちらを使うべきだとは思いますが、色々な制限でそれが叶わない場合の選択肢として、皆様の参考になれば幸いです。

コードの使用は自己責任でお願いいたします。

著者について

好きなAWSサービス:基本的なもの。低レイヤーなもの。
          EC2,KMS,S3,VPC,Cloudformation...
苦手なAWSサービス:複合的なもの。何かをラッピングしたもの(SAMとか?)
2024 Japan AWS All Certifications Engineers

大江 彰一朗をフォローする

クラウドに強いによるエンジニアブログです。

SCSKクラウドサービス(AWS)は、企業価値の向上につながるAWS 導入を全面支援するオールインワンサービスです。AWS最上位パートナーとして、多種多様な業界のシステム構築実績を持つSCSKが、お客様のDX推進を強力にサポートします。

AWSクラウドソリューション運用・監視
シェアする
タイトルとURLをコピーしました