AWS Systems Manager runbookを使ってAmazon EC2 インスタンスへのIPv4アドレスの自動割り当てを削除する
先日、別の記事で AWS Config → Amazon EventBridge → AWS Lambda を使って同様のことをしました。今回は、AWS Systems Manager の Automation runbook を使用して自動削除してみたいと思います。
Automation runbook を使うとそれほどプログラミングの知識がなくても統制を効かせることができるよという話をお伝えできればと思っています。
AWS Systems Manager Automation runbook を作成する
さて、実際に Systems Manager Automation runbook を作成してきましょう。
最初は下記のような画面が開かれます。 これはいま、 Design タブが選択されている状態です。
このようにグラフィカルインターフェースを使って runbook を作成することもできますが、 {} Code タブを選択し、 YAML もしくは JSON を直接編集することも可能です。
Automation runbook を書いていく
Automation runbook でビジュアル的にパーツを追加すると、YAML が記載されていくのがわかります。この YAML の中では Python を書くこともできます。例えばマネージドの runbook の AWS-BulkDeleteAssociation を見てみましょう。
下記のようにごりっごりの Python が書かれていますが結構地獄ですよね(個人の感想です)。これはスマートではないのと、この記事の目的としてはプログラミングの知識がない人でも実装できるということを示したいので Lambda も使わずできるということを証明したいと思います!
まずは一旦、最低限必要な要素だけを入れた runbook を作成することにします。
検索ボックスの下の "AWS APIs" タブを選択し、 "modifynetwork" と検索ボックスに入力すると今回使用する AWS API の ModifyNetworkInterfaceAttribute が出てきますので、それをドラッグ&ドロップで Start と End の間の真ん中に当てはめます。
{} Code タブを開いてみてください。下記のように Api として 「ModifyNetworkInterfaceAttribute」が叩かれる状態になっていることがわかります。
ここに引数を入れていきたいと思いますが、前回の記事の Lambda のコードをみても分かる通り、
NetworkInterfaceId を入れれば良さそうです。 API Reference はこちらです また Public IPv4 アドレスを自動でアサインしないようにするためには、 AssociatePublicIpAddress を false にする必要があることがわかります。
下記のように記載してみましょう。 NetworkInterfaceId の 「eni-」 から始まるのでそれとアルファベットと数字を合わせたものが入力される前提で正規表現を書いています。またパラメーターの description に (Required) を入力することで必須パラメーターとして扱われるようになります。 assumeRole はパラメーターで渡すことは必須ではありませんが常に書くことが推奨されています。それは、別アカウントから assume してこの runbook を使いたい場合があるからです。例えば CloudFormation StackSets などで runbook を各アカウントにばら撒いておいて、管理アカウントから assume してそれを実行するというユースケースがあります。
schemaVersion: '0.3'
description: Disable Public IPv4 on EC2.
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
AutomationAssumeRole:
type: String
description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
default: ''
NetworkInterfaceId:
type: String
description: (Required) Network Interface Id
allowedPattern: ^eni-[a-z0-9]+$
mainSteps:
- name: ModifyNetworkInterfaceAttribute
action: aws:executeAwsApi
isEnd: true
inputs:
Service: ec2
Api: ModifyNetworkInterfaceAttribute
NetworkInterfaceId: '{{ NetworkInterfaceId }}'
AssociatePublicIpAddress: false
さて、一旦ここで NewRunbook のところに適当な名前を入れて、 Create runbook を押してください。
Automation runbook をテスト実行してみる
作成した runbook を開いてみてください。 Owned by me タブの中に作成した runbook はあります
それを開くと Execute automation というのがあるのでクリックします
と、その前に EC2 インスタンスを作成しておいてください。
EC2 インスタンスの Network InterfaceId を確認しておきます。
eni- から始まる Network InterfaceId を Input parameters の Network InterfaceId のに入力して Execute をクリックしてみてください。
EC2 インスタンスの方の Network InterfaceId のパブリック IPアドレスが削除されているか確認してみましょう。消えていることがわかります。さて本当にこれで完了なのかはこれからみていきます。
Config の設定
ここは前回と同じく、"ec2-instance-no-public-ip" というマネージドルールを使っていきます。
この Config ルールを開いて、ルールの"修復の管理"から修復アクションを選択します。
自分で作成した Systems Manager のドキュメントを選択します。
Resource ID parameter として InstanceId しか渡せないことに気づきます。
あれ?本当はここで Network InterfaceId が渡したいのに出来ないようです。。
つまり、動的に Network InterfaceId を渡す術が無いようです。
EC2 インスタンス ID から Network InterfaceId のリストを取得する
仕方がないので方針を転換しましょう。 Network InterfaceId を渡すためには EC2 インスタンスから取得する必要があることがわかります。方法としては主に二つあります。一つ目が DescribeInstances で EC2 の情報をまるっと取得してきてごにょごにょする。二つ目が DescribeNetworkInterfaces で Network Interface の情報だけを取得するというものです。
今回は EC2 インスタンスの情報はほとんど使わないので Network Interface の情報だけを取得するため DescribeNetworkInterfaces API を叩いて情報を取ってくることにします。
ということで下記のように DescribeNetworkInterfaces API を利用して Network Interface の情報を取得するところを作っていきます。
コードは下記のようにしています。
schemaVersion: '0.3'
description: Disable Public IPv4 on EC2.
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
InstanceId:
type: String
description: The ID of the EC2 instance
AutomationAssumeRole:
type: String
description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
default: ''
mainSteps:
- name: DescribeNetworkInterfaces
action: aws:executeAwsApi
isEnd: true
inputs:
Service: ec2
Api: DescribeNetworkInterfaces
Filters:
- Name: attachment.instance-id
Values:
- '{{InstanceId}}'
outputs:
- Name: NetworkInterfaceIds
Selector: $.NetworkInterfaces..NetworkInterfaceId
Type: StringList
これで NetworkInterfaceId が StringList として取得できるようになります。が、ここで注目すべきところはまず DescribeNetworkInterfaces の API 仕様書を見ると Request Parameter が Filter.N と書かれているのですが YAML で書くとどう書くのかが最初わかりませんでした。たどり着いた答えが下記で、CLI の方の書き方に近かったです。
Filters:
- Name: attachment.instance-id
Values:
- '{{InstanceId}}'
もうひとつが、outputs の中の Selector です。
Selector: $.NetworkInterfaces..NetworkInterfaceId
この Selector ではどの値を output して次のステップで使うを指定することができます。 EC2 インスタンスには複数の Network Interface がついている可能性があるのでループで回す必要があるのですが、 Selector: $.NetworkInterfaces としてしまうと Type が MapList(連想配列みたいなもの) しか指定することができず、その場合 runbook のループを使うことができません。そのため StringList として配列を取得する必要があり、その方法として、「..」と書く方法があります。これを書くことによって、指定した項目の一覧を StringList として取得できるようになります。
該当の Network InterfaceId のパブリック IPv4 アドレス無効化していく
さて、ここまできたらあとは簡単です。下記のようなドキュメントを作りましょう。
最終的にコードは下記のようになります。
schemaVersion: '0.3'
description: Disable Public IPv4 on EC2.
assumeRole: '{{ AutomationAssumeRole }}'
parameters:
InstanceId:
type: String
description: The ID of the EC2 instance
AutomationAssumeRole:
type: String
description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf.
default: ''
mainSteps:
- name: DescribeNetworkInterfaces
action: aws:executeAwsApi
nextStep: Loop
isEnd: false
inputs:
Service: ec2
Api: DescribeNetworkInterfaces
Filters:
- Name: attachment.instance-id
Values:
- '{{InstanceId}}'
outputs:
- Name: NetworkInterfaceIds
Selector: $.NetworkInterfaces..NetworkInterfaceId
Type: StringList
- name: Loop
action: aws:loop
isEnd: true
inputs:
Iterators: '{{ DescribeNetworkInterfaces.NetworkInterfaceIds }}'
Steps:
- name: ModifyNetworkInterfaceAttribute
action: aws:executeAwsApi
isEnd: true
inputs:
Service: ec2
Api: ModifyNetworkInterfaceAttribute
NetworkInterfaceId: '{{ Loop.CurrentIteratorValue }}'
AssociatePublicIpAddress: false
"Create new version" をクリックしたあとで、 "Create new default version" を選択して新しいドキュメントを作成してください。
テストを行うに事前に EC2 インスタンスを立ち上げ、複数の Network Interface をアタッチしておきます。
それから、ドキュメントを開いて、 Execute automation をクリックして InstanceId を入力して Execute を押しましょう。
ちゃんと無効化されているか先ほどの画面を確認してみましょう。下の画像のように無効化されていれば成功です。
まとめ
いかがでしょう、プログラミングの知識がなくても統制を効かせられることがわかります。
ただ、こう書いてみるとあっさりできそう!と思われるかもしれませんし、それが狙いではある一方で、 yaml の書き方に本当に癖があり、色々と苦労しました。最初は Network Interface Id をリストで取得するには Python コードを書く必要があるのではないか?というところで進めていて、実はこのブログを書いた最初のバージョンでは実際そのようになっていました。
調べているなかで、「..」という書き方に気づき、今の形になっています。
何はともあれこういった runbook の書き方を蓄積していき誰でも AWS 環境の統制がとれる状況を目指して活動していこうと思います!
最後まで読んでくださりありがとうございます。
Discussion