【小ネタ】手動作成されたAWSリソースの設定差異をDictdifferで調査してみた

AWSリソースの設定差異を目検でチェックするのは辛いですよね
2021.02.09

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

CX事業本部@大阪の岩田です。先日IaC未導入の環境にて手動作成されたAWSリソースの設定差異を調査する機会がありました。PythonのライブラリDictdifferとboto3を組み合わせて比較的簡単に差分を特定できたので手順を簡単に紹介します。

環境

今回利用した環境です

  • Python: 3.8.3
  • boto3:1.17.4
  • botocore:1.20.4
  • Dictdiffer: 0.8.1

Dictdifferの最新リリースが2019年12月と少し古く、バージョンも1未満なのが少し気になりましたが今回試した範囲では問題なく利用できそうでした。

調査の背景

手動作成されたAWSリソースが大量に存在するIaC未導入の環境がありました。AWSリソースごとに

  • dev環境
  • staging環境
  • production環境

3つの環境でAWSリソースが全て手動作成されているという環境です。この環境でCloudFrontの設定変更が必要になり、事前に色々設定値を確認していたところ上記3環境の設定値が微妙にズレているということが分かりました。登録されているBehaviorsの数が違ったり、TTLの設定が微妙に異なったり...

設定値の変更やIaCの導入よりも先にまずは設定値の是正/統一が必要そうです。が、マネコンを開きながら設定差異を目検チェックするのは非効率極まりないので、もう少し機械的にチェックをかけたいところです。ちょっと調べたところPythonのDictdifferというライブラリで辞書オブジェクトの差分が簡単にチェックできるようでした。このライブラリとboto3を組み合わせればチェックが省力化できそうな気がします。ということでやってみます。

やってみる

まずはDictdifferをインストールします。

pip install dictdiffer

続いてPythonのインタプリタから設定差異を確認していきます

>>>from pprint import pprint
>>>import boto3
>>>from boto3.session import Session
>>>from dictdiffer import diff 
>>>
>>>profile = '<チェック対象環境のプロファイル名>'
>>>session = Session(profile_name=profile)
>>>cf = session.client('cloudfront')
>>>
>>>prd_distribution =  cf.get_distribution(Id='<prd環境のディストリビューションID>')['Distribution']
>>>stg_distribution =  cf.get_distribution(Id='<stg環境のディストリビューションID>')['Distribution']

Dictdifferのdiffを使って差分を出力します

>>>pprint(list(diff(prd_distribution, stg_distribution)))
[('change', 'Id', ('<prd環境のディストリビューションID>', '<stg環境のディストリビューションID>')),
 ('change',
  'ARN',
  ('<prd環境のARN>',
   '<stg環境のARN>')),
 ('change',
  'LastModifiedTime',
  (datetime.datetime(2020, 11, 6, 7, 1, 17, 213000, tzinfo=tzutc()),
   datetime.datetime(2020, 10, 22, 10, 28, 23, 750000, tzinfo=tzutc()))),
 ('change',
  'DomainName',
  ('xxx.cloudfront.net', 'yyy.cloudfront.net')),
 ('change',
  'DistributionConfig.CallerReference',
  ('1575813837009', '1574143882899')),
 ('change',
  ['DistributionConfig', 'Aliases', 'Items', 0],
  ('xxxxx.net', 'stg-xxxxx.net')),
 ('change',
  ['DistributionConfig', 'Aliases', 'Items', 1],
  ('*.xxxxx.net', '*.stg-xxxxx.net')),
  ...略

なんか色々出てきました。IdやARNについては差分があって当然なので、一部の項目については無視したいところです。差分検知の対象外としたい項目はdiffの名前付き引数ignoreで指定可能なので、ignore=['Id','ARN']と指定して再度差分を出力します。

>>>pprint(list(diff(prd_distribution, stg_distribution,ignore=['Id','ARN']))
[('change',
  'LastModifiedTime',
  (datetime.datetime(2020, 11, 6, 7, 1, 17, 213000, tzinfo=tzutc()),
   datetime.datetime(2020, 10, 22, 10, 28, 23, 750000, tzinfo=tzutc()))),
 ('change',
  'DomainName',
  ('xxx.cloudfront.net', 'yyy.cloudfront.net')),
 ('change',
  'DistributionConfig.CallerReference',
  ('1575813837009', '1574143882899')),
 ('change',
  ['DistributionConfig', 'Aliases', 'Items', 0],
  ('xxxxx.net', 'stg-xxxxx.net')),
 ('change',
  ['DistributionConfig', 'Aliases', 'Items', 1],
  ('*.xxxxx.net', '*.stg-xxxxx.net')),
 ('change',
  ['DistributionConfig', 'Origins', 'Items', 0, 'Id'],
  ('S3-hogehoge-fugafuga', 'ELB-stg-xxxxx-alb-123456789')),
 ('change',
  ['DistributionConfig', 'Origins', 'Items', 0, 'DomainName'],
  ('xxxxx-public.s3.amazonaws.com',
   'stg-xxxxx-alb-123456789.ap-northeast-1.elb.amazonaws.com')),
 ('add',
  ['DistributionConfig', 'Origins', 'Items', 0],
  [('CustomOriginConfig',
    {'HTTPPort': 80,
     'HTTPSPort': 443,
     'OriginKeepaliveTimeout': 180,
     'OriginProtocolPolicy': 'https-only',
     'OriginReadTimeout': 180,
     'OriginSslProtocols': {'Items': ['TLSv1.2'], 'Quantity': 1}})]),
...略

設定差異が分かりやすくなりました。もう少しignore対象を調整していくと良さそうですね。ちなみにignoreの対象を指定する際は辞書オブジェクトのキーを.でつないでいく記法が利用可能です。例えばignoreDistributionConfig.ViewerCertificate.ACMCertificateArnの指定を追加すると

>>>pprint(list(diff(prd_distribution, stg_distribution,ignore=['Id','ARN','DistributionConfig.ViewerCertificate.ACMCertificateArn'])))
...略
 ('change',
  'DistributionConfig.Logging.Prefix',
  ('cloudfront-xxx.yyy/logs/', '')),
 ('change',
  'DistributionConfig.ViewerCertificate.MinimumProtocolVersion',
  ('TLSv1.1_2016', 'TLSv1.2_2018')),
 ('change',
  'DistributionConfig.ViewerCertificate.Certificate',
  ('arn:aws:acm:us-east-1:012345678912:certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
   'arn:aws:acm:us-east-1:012345678912:certificate/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy')),
...略

このようにビュアーに設定されているACM証明書のARNが差分検知対象外となります。

DistributionConfig.ViewerCertificate.ACMCertificateArnの指定無し版はこちら

pprint(list(diff(prd_distribution, stg_distribution,ignore=['Id','ARN'])))
..略
 ('change',
  'DistributionConfig.ViewerCertificate.ACMCertificateArn',
  ('arn:aws:acm:us-east-1:012345678912:certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
   'arn:aws:acm:us-east-1:012345678912:certificate//yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy')),
 ('change',
  'DistributionConfig.ViewerCertificate.MinimumProtocolVersion',
  ('TLSv1.1_2016', 'TLSv1.2_2018')),
 ('change',
  'DistributionConfig.ViewerCertificate.Certificate',
  ('arn:aws:acm:us-east-1:012345678912:certificate/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
   'arn:aws:acm:us-east-1:012345678912:certificate/yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy')),
...略

.記法を有効活用すると差分チェックがはかどりそうです。

まとめ

諸事情によりIaCが未導入の環境で設定調査/変更する機会があれば参考にしてみて下さい。

参考