CI2部 技術2課の山﨑です。
前回のブログでは AWS Lambdaを使ってAWS Config Rules のカスタムルールを作成しました。
今回のブログでは AWS Lambdaの機能の1つであるLambda Layersを使ってカスタムルールとして定義したLambdaファンクションのコードをシンプルに書き換えたいと思います。
AWS Lambda Layersとは
以下、AWSドキュメントの引用です。
Lambda レイヤーは、Lambda 関数で使用できるライブラリとその他の依存関係をパッケージ化するための便利な方法を提供します。レイヤーを使用することで、アップロードされたデプロイメントアーカイブのサイズを削減し、コードをデプロイするスピードを速めることができます。 レイヤーは、追加のコードまたはデータを含むことができる .zip ファイルアーカイブです。レイヤーには、ライブラリ、 カスタムランタイム 、データ、または設定ファイルを含めることができます。レイヤーを使用すると、コードの共有と責任の分離を促進し、ビジネスロジックの記述をより迅速に繰り返すことができます。
Lambda レイヤーの作成と共有 - AWS Lambda
重要な点は「レイヤーを使用すると、コードの共有と責任の分離を促進し、ビジネスロジックの記述をより迅速に繰り返すことができます。」この部分です。
開発をしていると実行頻度が高い処理を「関数」に、実行頻度が高い関数を「モジュール」にすることでコードの再利用性を高めて不要なコーディングをしないように工夫するシーンがあると思います。
Lambda Layers はイメージとしてはこの「モジュール」を作る仕組みに似ています。様々なLambdaファンクションで利用する共通コンポーネントを「Layer」という形で事前定義することにより、複数のLambdaファンクションが参照できるようになります。
今回実装するLambda Layersの概要
切り出す共通コンポーネント
前回作成したConfig Rules 用に作成したLambdaファンクション内で定義している関数を列挙してみると、実際の評価ロジックを定義あるいは実行しているのは赤字の関数のみです。つまり、赤字以外の関数はConfig RulesのカスタムルールをLambdaファンクションを使って実装する上で利用可能な共通コンポーネントとなります。今回は赤字以外の関数をLambda Layersとして切り出します。
check_defined
get_client
get_assume_role_credentials
is_oversized_changed_notification
convert_api_configuration
get_configuration
get_configuration_item
is_applicable
check_ipv4Range_cidrs
evaluate_change_notification_compliance
lambda_handler
Lambda Layersの作成
共通コンポーネントをzipファイルにアーカイブ
コードの内容に大きな変更はありませんので中身のご紹介は割愛しますが、切り出した共通コンポーネントのファイルをzip化します。
Lambda ランタイムごとに、PATH 変数に /opt ディレクトリ内の特定のフォルダが含まれます。レイヤー .zip ファイルアーカイブに同じフォルダ構造を定義すると、関数コードはパスを指定しなくても、レイヤーコンテンツにアクセスできます。
Lambda レイヤーの作成と共有 - AWS Lambda
Lambda Layersのコードは Lambdaが実行されるコンテナのOS内では /opt 以下に展開されるため、上記AWSドキュメントに記載されているようにレイヤー .zip ファイルアーカイブに同じフォルダ構造を定義すると、関数コードはパスを指定しなくても、レイヤーコンテンツにアクセスできます。今回利用するPythonでは python/レイヤー.zip の構造を取ることでレイヤーコンテンツにアクセス可能になるためこれに倣います。
[Shohei@ ~]$ mkdir python [Shohei@ ~]$ cd python [Shohei@ ~/python]$ vi config_rules_layer.py # 共通コンポーネントとして切り出すコードを定義 [Shohei@ ~/python]$ cd [Shohei@ ~]$ zip -r config_rules_layer.zip python/ adding: python/ (stored 0%) adding: python/config_rules_layer.py (deflated 69%)
Lambda Layers にzipファイルをアップロード
Lambda Layersの作成をクリックします。
Lambda Layersの作成画面でzipファイルをアップロードし、ランタイムやCPUアーキテクチャを選択した後に作成を実行します。
Lambda Layersの作成はこれだけです。
LambdaファンクションからLayersを呼び出す
呼び出し元のLambdaファンクションの編集画面を開き、画面上部の図中に表示されている「Layers」をクリックします。
すると編集画面下部に移動するので「レイヤーの追加」をクリックします。
レイヤーの追加画面で作成したLayersを指定して追加します。
レイヤーが追加されるとLambdaファンクションの編集画面上部の「Layers」の数が増えます。
あとはLambda関数からLayersをインポートし、Layersから関数を呼び出すようにコードを修正すればOKです。Lambda Layers をインポートすることで実際の評価ロジックのコーディングに集中することが可能ですし、また元のコード(167行)の半分以下のコード量(78行)で収まりました。
import json import logging import config_rules_layer logger = logging.getLogger() logger.setLevel(logging.INFO) def check_ipv4Range_cidrs(inbound_permissions): for permission in inbound_permissions: logger.info(f'Permission: {permission}') if not permission['userIdGroupPairs']: for ipv4Range in permission['ipv4Ranges']: logger.info(f'ipv4Range: {ipv4Range}') if ipv4Range['cidrIp'] == '0.0.0.0/0': logger.warning('Inbound rule includes risks for accessing from unspecified hosts.') return ['NON_COMPLIANT', 'Inbound rule includes risks for accessing from unspecified hosts.'] return ['COMPLIANT', 'No Problem.'] else: return ['COMPLIANT', 'No Problem.'] def evaluate_change_notification_compliance(configuration_item, rule_parameters): try: config_rules_layer.check_defined(configuration_item, 'configuration_item') config_rules_layer.check_defined(configuration_item['configuration'], 'configuration_item[\'configuration\']') config_rules_layer.check_defined(configuration_item['configuration']['ipPermissions'], 'ipPermissions') inboundPermissions = configuration_item['configuration']['ipPermissions'] logger.info(f'InboundPermissions: {inboundPermissions}') if rule_parameters: config_rules_layer.check_defined(rule_parameters, 'rule_parameters') if configuration_item['resourceType'] != 'AWS::EC2::SecurityGroup': logger.info('Resource type is not AWS::EC2:SecurityGroup. This is ', configuration_item['resourceType'], '.') return ['NOT_APPLICABLE', 'Resource type is not AWS::EC2:SecurityGroup.'] if inboundPermissions == []: return ['COMPLIANT','Inbound rule does not have any permissions.'] else: logger.info('check_ipv4Range_cidrs') return check_ipv4Range_cidrs(inboundPermissions) except KeyError as error: logger.error(f'KeyError: {error} is not defined.') return ['NOT_APPLICABLE', 'Cannot Evaluation because object is none.'] def lambda_handler(event, context): global AWS_CONFIG_CLIENT AWS_CONFIG_CLIENT = config_rules_layer.get_client('config', event) invoking_event = json.loads(event['invokingEvent']) logger.info(f'invoking_event: {invoking_event}') rule_parameters = {} if 'ruleParameters' in event: rule_parameters = json.loads(event['ruleParameters']) configuration_item = config_rules_layer.get_configuration_item(invoking_event) logger.info(f'configuration_item: {configuration_item}') compliance_type = 'NOT_APPLICABLE' annotation = 'NOT_APPLICABLE.' if config_rules_layer.is_applicable(configuration_item, event): compliance_type, annotation = evaluate_change_notification_compliance( configuration_item, rule_parameters ) response = AWS_CONFIG_CLIENT.put_evaluations( Evaluations=[ { 'ComplianceResourceType': invoking_event['configurationItem']['resourceType'], 'ComplianceResourceId': invoking_event['configurationItem']['resourceId'], 'ComplianceType': compliance_type, 'Annotation': annotation, 'OrderingTimestamp': invoking_event['configurationItem']['configurationItemCaptureTime'] }, ], ResultToken=event['resultToken'] )
Config Rules のカスタムルールを実行してみるときちんと動作しました。
まとめ
今回はConfig Rules のカスタムルールという実例をもとにして、Lambda Layers を利用してみました。仮にカスタムルールを10個作成する、となった場合のことを想像してみると Lambda Layers で切り出した共通コンポーネントについては都度実装する必要がなくなるのでかなり省力化できるなと感じました。他にも利用可能なシーンは多数あると思いますので是非お試しください。
山﨑 翔平 (Shohei Yamasaki) 記事一覧はコチラ
カスタマーサクセス部所属。2019年12月にインフラ未経験で入社し、AWSエンジニアとしてのキャリアを始める。2023 Japan AWS Ambassadors/2023-2024 Japan AWS Top Engineers