「AWS無料相談会」をオンラインで開催中

実践・Systems Manager Automation〜「EC2のバックアップ取得からパッチ適用まで」を自動で〜

実践・Systems Manager Automation〜「EC2のバックアップ取得からパッチ適用まで」を自動で〜

はじめに

皆さんはEC2インスタンスでのパッチ適用はどのようにやっていますか?

  • 作業前にバックアップとしてAMIの取得
  • 該当のインスタンス内にSSH接続してコマンド実行によりパッチ適用

という具合でしょうか?
Patch Managerを使うことでパッチ適用はコマンド実行なく行えるかもしれませんが、「AMI取得完了を待ったのちに、パッチ適用」と言うのが案外時間がかかり、待たされるものです。

今回は、その待ち時間を解消するべく、自動化のシナリオ実現して見たいと思います。

やろうとしていること

シナリオとしては

  1. バックアップ用にEC2インスタンスのマシンイメージ(AMI)を取得する
  2. マシンイメージの取得が終わったら、パッチ適用を適用する

といった内容です。

2ステップだけですが、仮にいずれも手動で行っていた場合は、

マシンイメージ(AMI)の取得を開始する
→AMIの作成完了を待つ。(長さはさまざまで数分では終わらないこともある)
→時々「AMI取得終わったかな?」などを気にしながら、マネジメントコンソールの確認する。
→AMI取得が終わったのを見て、パッチ適用に取り掛かる

という具合で、AMI取得の完了まで待たされますし、パッチ適用をコマンドラインオペレーションで行っているような状況であれば、操作ミスによる危険性が少なからず存在した手順となってしまっています。

今回はこれらの自動化をして、開始したら放置できる、かつ安全に実行される体制を目指します。

事前準備

事前準備: EC2インスタンスの作成

まずは今回の検証用にEC2インスタンスを作成します。
2018年のAmazon Linux 2のイメージを使って作成することでパッチ適用対象が多く含まれた(パッチ適用のやりがいのある?)インスタンスを作成しました。
また、このインスタンスにはインスタンスロールもアタッチします。

このロールにはこのEC2インスタンスをSystems Managerでの管理ができるようにするための

  • AmazonSSMManagedInstanceCore

とCloudWatchLogsでパッチ適用時のログ記録を行えるようにするために

  • CloudWatchLogsFullAccess

のポリシーを付与しておきます。(※実際の運用時には”FullAccess”ではなく、厳密な最小権限で付与するようにしてください。)

さて、この状態でパッチ対象を確認してみると・・・・

$ sudo yum update
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                       | 3.7 kB  00:00:00
Resolving Dependencies
--> Running transaction check

(中略)

Transaction Summary
========================================================================================================================================
Install    4 Packages (+14 Dependent packages)
Upgrade  320 Packages

Total download size: 146 M
Is this ok [y/d/N]:

期待通りとても大量にアップデート対象が出てきました!

これでEC2の作成は準備完了です。

事前準備: Automation “Runbook”の作成

次にSystems Manager Automationの実行内容が定義された “Runbook”を作成します。
Systems Managerの「ドキュメント」の画面から作成を選びます。

そして、エディタ画面を開いて次のyamlの内容を保存

description: ''
schemaVersion: '0.3'
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
  InstanceId:
    type: String
    description: ' (Required) Target EC2 InstanceId'
    allowedPattern: '^i-[a-z0-9]{8,17}$'
  AutomationAssumeRole:
    type: String
    description: (Required) The ARN of the role that allows Automation to perform the actions on your behalf.
mainSteps:
  - name: InstanceIdListed
    action: 'aws:executeScript'
    inputs:
      Runtime: python3.7
      Handler: script_handler
      Script: |-
        def script_handler(events, context):
          mylist =[]
          mylist.append(events["InstanceId"])
          return (mylist)
      InputPayload:
        InstanceId: '{{InstanceId}}'
    outputs:
      - Name: result
        Selector: $.Payload
        Type: StringList
  - name: GetInstanceName
    action: 'aws:executeScript'
    inputs:
      Runtime: python3.7
      Handler: script_handler
      Script: |
        import boto3

        def script_handler(events, context):
            instanceId = events["InstanceId"]
            ec2 = boto3.client('ec2')
            tags = ec2.describe_tags(
                Filters=[{'Name': 'resource-id', 'Values': [instanceId]}])['Tags']
            if not tags:
                return (instanceId)
            for tag in tags:
                if (tag['Key']) == "Name":
                    result = tag['Value']
            return (result)
      InputPayload:
        InstanceId: '{{InstanceId}}'
    outputs:
      - Name: result
        Selector: $.Payload
        Type: String
  - name: CreateAMI
    action: 'aws:createImage'
    inputs:
      InstanceId: '{{InstanceId}}'
      ImageName: '{{GetInstanceName.result}}_{{global:DATE}}_{{automation:EXECUTION_ID}}'
  - name: RunPatchBaseline
    action: 'aws:runCommand'
    inputs:
      DocumentName: AWS-RunPatchBaseline
      InstanceIds: '{{InstanceIdListed.result}}'
      Parameters:
        Operation: Install
        SnapshotId: '{{automation:EXECUTION_ID}}'
      CloudWatchOutputConfig:
        CloudWatchOutputEnabled: true

これでAutomationの設定も完成です。

事前準備: Automation実行用のIAMロールの作成

Automationは完成したのですが、もう少しだけ準備が必要です。
Automationのステップの中にはPythonのboto3を使って、EC2インスタンス情報を取得( describe_tags )している箇所があります。
そのため、デフォルトでは権限が足りないため別途新設してそれを使用する必要があります。

作成画面よりSystems Managerサービス用のIAMロールを作成します

そしてこのロールには
Automation実行のために最低限必要な

  • AmazonSSMAutomationRole

と、それにプラスして

  • AmazonEC2ReadOnlyAccess(この権限をEC2インスタンス情報取得のために使う)

のポリシーを付与します。(※EC2の部分は実際の運用時にはより厳密な最小権限で付与するようにしてください。)


これで実行用のIAMロールが完成です。実行時に使うのでARNを控えておきます。

事前準備:パッチグループの設定

事前準備は次が最後です。
今回の自動化におけるパッチ適用は具体的には RunPatchBaseline というRunCommandの仕組みを呼び出すことで実現されます。

そのためそのパッチ適用時には、そのEC2インスタンスが適用されるパッチベースラインに準じて適用されます。

細かい解説は省いてしまうのですが、今回は “My-Baseline”というベースライン、パッチグループをEC2インスタンスに適用します。
そして

  • 「配信された全てのパッチを即時適用する」

という設定を以下のように行います。

ベースラインの内訳:

EC2のタグ指定によりパッチグループ指定:

以上でパッチ適用時の動作も確定しました。

各ステップの実行結果と解説

では以上を踏まえて実行してみましょう。

必要事項の入力と実行

先ほど完成したAutomationの画面から「オートメーションを実行する」を押下

次にインスタンスIDと”AutomationAssumeRole”の入力が求められるので、EC2インスタンスのIDと、先ほど作成したAutomation実行用のロールを指定します。


どちらも選択式のフォームになっているので入力が便利ですし、不正な値は入れられないようになっています。

入力を終えたら「実行」を押下します。

ステップ1・2

実行したら次のような画面になります。

さて、このステップ1・2では後続の処理で使用するために

  • 入力されたインスタンスIDを“StringList”化
  • 該当するインスタンスの”Name”タグの値を取得

を行っています。ここはすぐ終わります。
完了したら次のステップに進みます。

ステップ3


こちらでは入力されたインスタンスIDを対象にマシンイメージ(AMI)の作成を行います。
その際にはステップ3では上記で取得された”Name”タグ(もしなかった場合はインスタンスID)を使って
<Name>_<実行年月日>_<実行時のID>

のような表記でAMIが作成されます。
このステップが動いているときにAMIの画面を開くと、確かにAMIの作成が行われていることがわかります。

これも完了したら自動的に次のステップに進みます。

ステップ4

最後のステップでパッチ適用が行われます。

この際の詳細な結果は”Run Command”の履歴からも見ることでき、希望したEC2インスタンスでのパッチ適用が行われてたということがわかります。
(詳細な実行ログはCloudWatchLogsに記録されており、万が一の際のトラブルシュートもできるようになっています。)

そして、インスタンス内からもパッチ適用状況を確認してみると、

[ec2-user@ip-172-xx-xx-xxx ~]$ sudo yum update
Loaded plugins: extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                       | 3.7 kB  00:00:00
No packages marked for update
[ec2-user@ip-172-xx-xx-xxx ~]$

“No packages marked for update”!
全てのパッケージが無事アップデートされていました。
アップデート時には再起動も自動的に行われるので、全て完了です。

まとめ

実行までにいくらか準備が必要なものでありましたが、今回の設定によりインスタンスIDと実行用ロールを自由に指定するだけであとはパッチ適用まで行ってくれる仕組みが出来上がりました。

AMIの取得は時に長く時間がかかる場合があるので、その間の「作成終わったかな?」の確認を繰り返す必要がなくなる、完了次第パッチ適用が自動的に始まる、というのは手間の削減に大きく貢献するのではないでしょうか。
値の入力が選択式のフォームになっており、オペレーションが全て自動化されるのでミスをする可能性を排してより安全な運用につながります。

ちなみに、下図の通りAutomation実行画面までの専用のURLも共有しやすい形で生成されるので、定型業務の共有も非常にしやすくなります。

今回は、Runbookの内容については、いきなりyamlを掲示しましたが、実際には「ビルダー」の画面で必要事項を入力していきながら作成ができ、例えば「AMIの作成」や「パッチ適用の実行」のようなシンプルな操作であれば、プログラミング言語の知識がなくとも処理ステップを作っていけます。
・・・いわゆる「ノーコード」とまではまだ難しいかもしれませんが、Automationを使えば、Lambda関数を新設せずとも自動化を実現できることは多いはずです。

引き続き利便性向上のためにAutomation活用のアイディアを考えていきたいと思います。