EC2, Ansible і SSM: хто кого?
Привіт! Мене звати Artem Hrechanychenko. Я Lead SRE в TEMABIT Software Development та AWS Community Builder. У вільний час я веду різні навчальні активності в DevOps 01 та YouTube-канал DevOps01, де ділюсь практичним досвідом з AWS, Kubernetes, CI/CD, Terraform і волонтерю для ЗСУ. Якщо вам цікавий розбір інструментів — цей пост для вас.
Ansible, знімай накидку — ми тебе впізнали! 😄
Натхненний AWS Skill Builder лабою «Using the AWS Systems Manager Run Command for Automation», я вирішив трошки препарувати AWS Systems Manager (SSM) Run Command, порівняти з Ansible і показати, що там насправді відбувається «під капотом». Бо будь-який новий інструмент легше зрозуміти, якщо його порівняти з тим, що вже знайоме.
EC2: що ми з ним зазвичай робимо?
- Post-installation configuration (встановлення софту, конфіги, користувачі);
- Day 2 operations (patching, security hardening, etc).
Як це можна вирішити?
✅ Post-installation configuration: user-data
Класика. Пхаємо shell-скрипт при створенні інстансу — і воно щось виконує.
📁 Скрипт потрапляє сюди:
/var/lib/cloud/instances/<instance-id>/
📄 Лог — тут:
/var/log/cloud-init-output.log
⚠️ Мінуси:
- Працює лише 1 раз при створенні.
- Щоб побачити результат — треба лізти на сам інстанс.
- Якщо щось не так — дебажити незручно.
🛠️ Day 2 operations: Ansible
Це вже потужніше. Створюємо playbook, наприклад:
- name: Restart nginx hosts: web tasks: - name: Restart service ansible.builtin.command: apk||dnf|| whaterver cli - update something
Працює — так. Гнучко — так. Але треба:
- Менеджер-ноду з Ansible (GitLab runner, EC2, контейнер...). Нам потрібно десь екзек’ютити ansible-playbook процес.
- Inventory (динамічний або статичний). Якось заповнити, відфільтрувати по тегам?
- Відкритий SSH (і ключі до нього). Можна обходити, взявши охапку костилів — ssh на іншому порті, гітлаб ране в приватні мережі, тоді сек’юріті-групу не потрібно проколювати в публічному сабнеті.
⚠️ Мінуси:
- Треба підтримувати ssh-доступи.
- Відкриті порти = ризики.
- Python на таргеті обов’язково.
- Це не модно 😏
🎭 А тут з’являється SSM
Ansible, знімай маску — ми тебе впізнали 😏 Шуткую, але за функціональним призначенням SSM може вирішувати всі ті задачі, що і Ansible.
SSM — це не push, а pull-модель:
- SSM-agent (встановлений в Amazon Linux Або достав самостійно).
- EC2 повинен мати IAM-роль AmazonSSMManagedInstanceCore.
- Агент повинен достукатись до SSM endpoint (через NAT до публічного або VPC endpoint for SSM*).
🔎 Як це виглядає в SSM:
- Керуєш зоопарком машин через Fleet Manager або Resource Groups.
- Обираєш інстанси по для запуску по тегах/instance-id/resource group.
- Запускаєш Run Command або Automation.
- Все логується, все моніториться, все свистить — SSH не потрібен.

🧪 Демка: встановлення apache + curl для тесту
Мета: встановити Apache, створити index.html з INSTANCE ID і зробити curl localhost (простий приклад — так, але давайте ускладнення вже в інший раз).
Скрипт, який передаємо в AWS-RunShellScript:

#!/bin/bash TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" \ -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" \ http://169.254.169.254/latest/meta-data/instance-id) yum install -y httpd echo "<h1>Hello, from Apache! INSTANCE ID: $INSTANCE_ID</h1>" > /var/www/html/index.html systemctl enable httpd systemctl start httpd curl localhost
Інтерфейс SSM дозволяє обрати інстанси за тегом або resource group.


🔧 А що під капотом?

SSM-agent — це systemd-сервіс:
systemctl status amazon-ssm-agent amazon-ssm-agent.service - amazon-ssm-agent Loaded: loaded (/usr/lib/systemd/system/amazon-ssm-agent.service; enabled; preset: enabled) Active: active (running) since Mon 2025-03-24 21:21:09 UTC; 49min ago Main PID: 2142 (amazon-ssm-agen) Tasks: 34 (limit: 1111) Memory: 200.7M CPU: 2min 21.739s CGroup: /system.slice/amazon-ssm-agent.service ├─ 2142 /usr/bin/amazon-ssm-agent ├─ 2189 /usr/bin/ssm-agent-worker ├─65202 /usr/bin/ssm-session-worker [email protected] └─65214 sh
Вcі логи дивимось в:
- «/var/log/amazon/ssm/amazon-ssm-agent.log»
- #tail -F /var/log/amazon/ssm/amazon-ssm-agent.log
2025-03-24 23:13:07.7055 INFO [ssm-session-worker] [a.hrechanychenko@x-6y2engpcr3t4zij86bn7g53v5q] [DataBackend] [pluginName=Standard_Stream] Successfully opened websocket connection to: 52.94.138.245:443 2025-03-24 23:13:07.7057 INFO [ssm-session-worker] [a.hrechanychenko@tx-6y2engpcr3t4zij86bn7g53v5q] [DataBackend] [pluginName=Standard_Stream] Skipping handshake. 2025-03-24 23:13:07.7059 INFO [ssm-session-worker] [a.hrechanychenko@x-6y2engpcr3t4zij86bn7g53v5q] [DataBackend] [pluginName=Standard_Stream] Starting websocket pinger 2025-03-24 23:13:07.7059 INFO [ssm-session-worker] [a.hrechanychenko@tx-6y2engpcr3t4zij86bn7g53v5q] [DataBackend] [pluginName=Standard_Stream] Starting websocket listener 2025-03-24 23:13:07.7059 INFO [ssm-session-worker] [a.hrechanychenko@tx-6y2engpcr3t4zij86bn7g53v5q] [DataBackend] [pluginName=Standard_Stream] Starting command executor 2025-03-24 23:13:07.7113 INFO [ssm-session-worker] [a.hrechanychenko@x-6y2engpcr3t4zij86bn7g53v5q] [DataBackend] [pluginName=Standard_Stream] ssm-user already exists. 2025-03-24 23:13:07.7209 INFO [ssm-session-worker] [a.hrechanychenko@tx-6y2engpcr3t4zij86bn7g53v5q] [DataBackend] [pluginName=Standard_Stream] Plugin Standard_Stream started 2025-03-24 23:13:57.0578 INFO [ssm-agent-worker] [HealthCheck] HealthCheck reporting agent health. 2025-03-24 23:13:57.0639 INFO [ECSIdentity] Agent not taking ECS identity: Could not fetch metadata endpoint 2025-03-24 23:16:10.8471 INFO [ssm-agent-worker] [MessageService] [Association] Next association is scheduled at 2025-03-24 23:29:30.947 +0000 UTC, association will wait for 13m20.099848939s
Ось тобі SSM Document у JSON-форматі:
025-03-24 23:17:53.3682 INFO [ssm-agent-worker] [MessageService] [MGSInteractor] Processing AgentMessage: MessageType - agent_job, Id - 4b961fa0-590c-460b-b979-bc45f14c5ee8
2025-03-24 23:17:53.3794 INFO ssm-document-worker - v3.3.1611.0
2025-03-24 23:17:53.3683 INFO [ssm-agent-worker] [MessageService] [MGSInteractor] Parsing AgentMessage 4b961fa0-590c-460b-b979-bc45f14c5ee8, Payload: {"schemaVersion":1,"jobId":"aws.ssm.1168009f-5ac5-48a3-85f8-799f786fd419.i-0cded606e0fff645c","topic":"aws.ssm.sendCommand","content":"{\"OutputS3KeyPrefix\":\"\",\"CloudWatchOutputEnabled\":\"false\",\"Parameters\":{\"executionTimeout\":\"3600\",\"workingDirectory\":\"\",\"commands\":[\"#!/bin/bash\",\"\",\"#Creates token to authenticate and retrieve instance metadata\",\"TOKEN\u003d`curl -X PUT \\\"http://169.254.169.254/latest/api/token\\\" -H \\\"X-aws-ec2-metadata-token-ttl-seconds: 21600\\\"`\",\" \",\"#Variable for instance ID\",\"INSTANCE_ID\u003d$(curl -H \\\"X-aws-ec2-metadata-token: $TOKEN\\\" -v http://169.254.169.254/latest/meta-data/instance-id)\",\"\",\"# Install Apache and configure the index.html file\",\"yum install -y httpd\",\"echo \\\"\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003ch4 align\u003d\\\"Center\\\"\u003eINSTANCE ID: $INSTANCE_ID\u003c\\/h4\u003e\u003cbr\u003e\u003ch1 align\u003d\\\"Center\\\"\u003eHello, from Apache!\u003c\\/h1\u003e\\\" \u003e /var/www/html/index.html\",\"\",\"# Enable and start Apache\",\"systemctl enable httpd\",\"systemctl start httpd\"]},\"DocumentContent\":{\"schemaVersion\":\"1.2\",\"description\":\"Run a shell script or specify the commands to run.\",\"runtimeConfig\":{\"aws:runShellScript\":{\"properties\":[{\"workingDirectory\":\"{{ workingDirectory }}\",\"timeoutSeconds\":\"{{ executionTimeout }}\",\"runCommand\":\"{{ commands }}\",\"id\":\"0.aws:runShellScript\"}]}},\"parameters\":{\"executionTimeout\":{\"default\":\"3600\",\"description\":\"(Optional) The time in seconds for a command to complete before it is considered to have failed. Default is 3600 (1 hour). Maximum is 172800 (48 hours).\",\"type\":\"String\",\"allowedPattern\":\"([1-9][0-9]{0,4})|(1[0-6][0-9]{4})|(17[0-1][0-9]{3})|(172[0-7][0-9]{2})|(172800)\"},\"workingDirectory\":{\"default\":\"\",\"description\":\"(Optional) The path to the working directory on your instance.\",\"maxChars\":4096,\"type\":\"String\"},\"commands\":{\"minItems\":1,\"displayType\":\"textarea\",\"description\":\"(Required) Specify a shell script or a command to run.\",\"type\":\"StringList\"}}},\"CloudWatchLogGroupName\":\"\",\"OutputS3Region\":\"eu-central-1\",\"CommandId\":\"1168009f-5ac5-48a3-85f8-799f786fd419\",\"OutputS3BucketName\":\"\",\"DocumentName\":\"AWS-RunShellScript\"}"}
Аутентифікується в IAM та каже — я пішов робити таску!
025-03-24 23:17:53.3795 INFO picking up runtime config identity selector
2025-03-24 23:17:53.3688 INFO [ssm-agent-worker] [MessageService] [EngineProcessor] document aws.ssm.1168009f-5ac5-48a3-85f8-799f786fd419.i-0cded606e0fff645c submission started
2025-03-24 23:17:53.3795 INFO Checking if agent identity type OnPrem can be assumed
2025-03-24 23:17:53.3691 INFO [ssm-agent-worker] [MessageService] [EngineProcessor] document aws.ssm.1168009f-5ac5-48a3-85f8-799f786fd419.i-0cded606e0fff645c submission ended
2025-03-24 23:17:53.3796 INFO Checking if agent identity type EC2 can be assumed
2025-03-24 23:17:53.3691 INFO [ssm-agent-worker] [MessageService] [CommandProcessorWrapper] [Idempotency] writing command in the idempotency directory for command 1168009f-5ac5-48a3-85f8-799f786fd419
2025-03-24 23:17:53.4210 INFO Agent will take identity from EC2
025-03-24 23:17:53.3692 INFO [ssm-agent-worker] [MessageService] [MGSInteractor] Sending reply {
"additionalInfo": {
"agent": {
"lang": "en-US",
"name": "amazon-ssm-agent",
"os": "",
"osver": "1",
"ver": ""
},
"dateTime": "2025-03-24T23:17:53.369Z",
"runId": "",
"runtimeStatusCounts": null
},
"documentStatus": "InProgress",
"documentTraceOutput": "",
"runtimeStatus": null
}
Конвертує SSM Document у JSON-форматі від SSM Service в shell-скрипт у каталог відповідно до темплейту нашого SSM Document. Бо потрібно смикнути потрібний «провайдер/plugin» — aws:runShellScript:
025-03-24 23:17:53.4210 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] document: 1168009f-5ac5-48a3-85f8-799f786fd419 worker started
2025-03-24 23:17:53.3709 INFO [ssm-agent-worker] [MessageService] [EngineProcessor] [BasicExecuter] [1168009f-5ac5-48a3-85f8-799f786fd419] inter process communication started at /var/lib/amazon/ssm/i-0cded606e0fff645c/channels/1168009f-5ac5-48a3-85f8-799f786fd419
...
2025-03-24 23:17:53.3787 INFO [ssm-agent-worker] [MessageService] [MGSInteractor] Processing AgentMessage: MessageType - agent_job_reply_ack, Id - 3784ef6d-e302-4f9d-8452-51d492b4623f
...
2025-03-24 23:17:53.4221 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] Successfully loaded platform dependent plugin aws:runShellScript
...
2025-03-24 23:17:53.4221 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] inter process communication started at /var/lib/amazon/ssm/i-0cded606e0fff645c/channels/1168009f-5ac5-48a3-85f8-799f786fd419
2025-03-24 23:17:53.4396 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] [DataBackend] received plugin config message
2025-03-24 23:17:53.4397 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] [DataBackend] {"DocumentInformation":{"DocumentID":"1168009f-5ac5-48a3-85f8-799f786fd419","CommandID":"1168009f-5ac5-48a3-85f8-799f786fd419","AssociationID":"","InstanceID":"i-0cded606e0fff645c","MessageID":"aws.ssm.1168009f-5ac5-48a3-85f8-799f786fd419.i-0cded606e0fff645c","RunID":"2025-03-24T23-17-53.368Z","CreatedDate":"57198-12-24 18:09:26 +0000 UTC","DocumentName":"AWS-RunShellScript","DocumentVersion":"","DocumentStatus":"InProgress","RunCount":0,"ProcInfo":{"Pid":69052,"StartTime":"2025-03-24T23:17:53.369702231Z"},"ClientId":"","RunAsUser":"","SessionOwner":""},"DocumentType":"SendCommand","SchemaVersion":"1.2","InstancePluginsInformation":[{"Configuration":{"Settings":null,"Properties":[{"id":"0.aws:runShellScript","runCommand":["#!/bin/bash","","#Creates token to authenticate and retrieve instance metadata","TOKEN=`curl -X PUT \"http://169.254.169.254/latest/api/token\" -H \"X-aws-ec2-metadata-token-ttl-seconds: 21600\"`"," ","#Variable for instance ID","INSTANCE_ID=$(curl -H \"X-aws-ec2-metadata-token: $TOKEN\" -v http://169.254.169.254/latest/meta-data/instance-id)","","# Install Apache and configure the index.html file","yum install -y httpd","echo \"\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\u003ch4 align=\"Center\"\u003eINSTANCE ID: $INSTANCE_ID\u003c/h4\u003e\u003cbr\u003e\u003ch1 align=\"Center\"\u003eHello, from Apache!\u003c/h1\u003e\" \u003e /var/www/html/index.html","","# Enable and start Apache","systemctl enable httpd","systemctl start httpd"],"timeoutSeconds":"3600","workingDirectory":""}],"OutputS3KeyPrefix":"1168009f-5ac5-48a3-85f8-799f786fd419/i-0cded606e0fff645c/awsrunShellScript","OutputS3BucketName":"","S3EncryptionEnabled":false,"CloudWatchLogGroup":"","CloudWatchEncryptionEnabled":false,"CloudWatchStreamingEnabled":false,"OrchestrationDirectory":"/var/lib/amazon/ssm/i-0cded606e0fff645c/document/orchestration/1168009f-5ac5-48a3-85f8-799f786fd419/awsrunShellScript","MessageId":"aws.ssm.1168009f-5ac5-48a3-85f8-799f786fd419.i-0cded606e0fff645c","BookKeepingFileName":"1168009f-5ac5-48a3-85f8-799f786fd419","PluginName":"aws:runShellScript","PluginID":"aws:runShellScript","DefaultWorkingDirectory":"","Preconditions":null,"IsPreconditionEnabled":false,"CurrentAssociations":null,"SessionId":"","ClientId":"","KmsKeyId":"","RunAsEnabled":false,"RunAsUser":"","ShellProfile":{"windows":"","linux":""},"SessionOwner":"","UpstreamServiceName":""},"Name":"aws:runShellScript","Result":{"pluginID":"","pluginName":"","status":"","code":0,"output":null,"startDateTime":"0001-01-01T00:00:00Z","endDateTime":"0001-01-01T00:00:00Z","outputS3BucketName":"","outputS3KeyPrefix":"","stepName":"","error":"","standardOutput":"","standardError":""},"Id":"aws:runShellScript"}],"CancelInformation":{"CancelMessageID":"","CancelCommandID":"","Payload":"","DebugInfo":""},"IOConfig":{"OrchestrationDirectory":"/var/lib/amazon/ssm/i-0cded606e0fff645c/document/orchestration/1168009f-5ac5-48a3-85f8-799f786fd419","OutputS3BucketName":"","OutputS3KeyPrefix":"1168009f-5ac5-48a3-85f8-799f786fd419/i-0cded606e0fff645c","CloudWatchConfig":{"LogGroupName":"","LogStreamPrefix":"","LogGroupEncryptionEnabled":false}},"UpstreamServiceName":"MessageGatewayService"}
025-03-24 23:17:53.4400 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] [DataBackend] Running plugin aws:runShellScript aws:runShellScript
2025-03-24 23:17:53.4401 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] [DataBackend] [pluginName=aws:runShellScript] aws:runShellScript started with configuration {<nil> map[id:0.aws:runShellScript runCommand:[#!/bin/bash #Creates token to authenticate and retrieve instance metadata TOKEN=`curl -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 21600"` #Variable for instanceID INSTANCE_ID=$(curl -H "X-aws-ec2-metadata-token: $TOKEN" -v http://169.254.169.254/latest/meta-data/instance-id) # Install Apache and configure the index.html file yum install -y httpd echo "<br><br><br><h4 align="Center">INSTANCE ID: $INSTANCE_ID</h4><br><h1 align="Center">Hello, from Apache!</h1>" > /var/www/html/index.html # Enable and start Apache systemctl enable httpd systemctl start httpd] timeoutSeconds:3600 workingDirectory:] 1168009f-5ac5-48a3-85f8-799f786fd419/i-0cded606e0fff645c/awsrunShellScript false false false /var/lib/amazon/ssm/i-0cded606e0fff645c/document/orchestration/1168009f-5ac5-48a3-85f8-799f786fd419/awsrunShellScript aws.ssm.1168009f-5ac5-48a3-85f8-799f786fd419.i-0cded606e0fff645c 1168009f-5ac5-48a3-85f8-799f786fd419 aws:runShellScript aws:runShellScript map[] false [] false { } MessageGatewayService}
Все готово!
2025-03-24 23:17:54.2318 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] [DataBackend] Sending plugin aws:runShellScript completion message 2025-03-24 23:17:54.2320 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] [DataBackend] document execution complete 2025-03-24 23:17:54.2320 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] [DataBackend] sending document complete response... 2025-03-24 23:17:54.2321 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] [DataBackend] stopping ipc worker... 2025-03-24 23:17:54.2321 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] requested shutdown, prepare to stop messaging 2025-03-24 23:17:54.2322 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] worker listener stopped on path: /var/lib/amazon/ssm/i-0cded606e0fff645c/channels/1168009f-5ac5-48a3-85f8-799f786fd419 2025-03-24 23:17:54.2326 INFO [ssm-document-worker] [1168009f-5ac5-48a3-85f8-799f786fd419] document worker closed 2025-03-24 23:17:53.3787 WARN [ssm-agent-worker] [MessageService] [MGSInteractor] acknowledgement b2359a31-31a5-4c21-82b9-7964dab01ca3 received but could not find any reply threads running 2025-03-24 23:17:54.2326 INFO [ssm-agent-worker] [MessageService] [EngineProcessor] [BasicExecuter] [1168009f-5ac5-48a3-85f8-799f786fd419] requested terminate messaging worker, destroying the channel 2025-03-24 23:17:54.2333 INFO [ssm-agent-worker] [MessageService] [EngineProcessor] sending document: 1168009f-5ac5-48a3-85f8-799f786fd419 complete response 2025-03-24 23:17:54.2333 INFO [ssm-agent-worker] [MessageService] [CommandProcessorWrapper] received plugin: aws:runShellScript result from Processor 2025-03-24 23:17:54.2333 INFO [ssm-agent-worker] [MessageService] [CommandProcessorWrapper] command: aws.ssm.1168009f-5ac5-48a3-85f8-799f786fd419.i-0cded606e0fff645c complete
SSM Agent аутентифікується через EC2 Instance profile, пулить команду, створює скрипт, виконує, і надсилає результат назад.

📦 Плюшки
- Працює без SSH.
- IAM-управління доступом.
- Можна запускати по EventBridge.
- Automation підтримує approvals, rollback, параметри.
- Можна зберігати логи в CloudWatch або S3 (потребує більш розширену IAM-роль, ніж AWS SSM Instance Core).
🧠 Висновок
SSM Run Command & Automation вирішують всі звичні нам задачі.
- запуск adhoc commands;
- scheduling & cron based automation;
- even based automation (раніше трігерили пайплайн по коміту/вебхуку — маєте evenbridge and ssm target);
- SSM це не тільки Parameter Store та заміна SSH connection вашому бастіон-хосту.
🎯 Якщо ти вже в AWS — SSM може покрити 80% задач без Ansible / user-data.

4 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів