<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>IaC | 伊東屋TECHブログ</title>
	<atom:link href="https://tech-itoya.com/tag/iac/feed/" rel="self" type="application/rss+xml" />
	<link>https://tech-itoya.com</link>
	<description>千葉県・船橋市の総合園芸店が運営する技術ブログ</description>
	<lastBuildDate>Sun, 13 Oct 2024 07:54:10 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9</generator>

<image>
	<url>https://tech-itoya.com/wp-content/uploads/2024/10/cropped-itoya-logo-32x32.png</url>
	<title>IaC | 伊東屋TECHブログ</title>
	<link>https://tech-itoya.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>CloudFormationスタックから外れたサブネットをスタック管理下に引き戻したい</title>
		<link>https://tech-itoya.com/cloudformation%e3%82%b9%e3%82%bf%e3%83%83%e3%82%af%e3%81%8b%e3%82%89%e5%a4%96%e3%82%8c%e3%81%9f%e3%82%b5%e3%83%96%e3%83%8d%e3%83%83%e3%83%88%e3%82%92%e3%82%b9%e3%82%bf%e3%83%83%e3%82%af%e7%ae%a1/</link>
					<comments>https://tech-itoya.com/cloudformation%e3%82%b9%e3%82%bf%e3%83%83%e3%82%af%e3%81%8b%e3%82%89%e5%a4%96%e3%82%8c%e3%81%9f%e3%82%b5%e3%83%96%e3%83%8d%e3%83%83%e3%83%88%e3%82%92%e3%82%b9%e3%82%bf%e3%83%83%e3%82%af%e7%ae%a1/#respond</comments>
		
		<dc:creator><![CDATA[アーサー・C・ダントー]]></dc:creator>
		<pubDate>Sun, 13 Oct 2024 07:54:09 +0000</pubDate>
				<category><![CDATA[TECH記事]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[CloudFormation]]></category>
		<category><![CDATA[IaC]]></category>
		<guid isPermaLink="false">https://tech-itoya.com/?p=134</guid>

					<description><![CDATA[こんにちは。ダントーです。 CloudFormationスタックで管理していたサブネットが、スタックの更新・削除により意図せずスタック管理下から外れてしまって焦っている方向けの記事です。 他リソースが依存しているサブネッ [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>こんにちは。ダントーです。</p>



<p>CloudFormationスタックで管理していたサブネットが、スタックの更新・削除により意図せずスタック管理下から外れてしまって焦っている方向けの記事です。</p>



<p>他リソースが依存しているサブネットをスタックから削除しようとすると、<strong>スタックから消えないだけでなく<span class="marker-under">スタック管理下から外れる</span></strong>といった現象に出くわしたので、その際の対応を備忘録として残します。</p>



<p></p>



<p><strong>想定検索ワード</strong>：CloudFormation  スタック  サブネット  外れる</p>




  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-2" checked><label class="toc-title" for="toc-checkbox-2">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">TL; DR;</a></li><li><a href="#toc2" tabindex="0">構成図とCloudFormationテンプレート</a></li><li><a href="#toc3" tabindex="0">本題</a><ol><li><a href="#toc4" tabindex="0">状況を再現(スタックを作成する)</a></li><li><a href="#toc5" tabindex="0">状況を再現(スタック管理下からサブネットを外す)</a></li><li><a href="#toc6" tabindex="0">本題：サブネットをスタック管理下に引き戻す</a><ol><li><a href="#toc7" tabindex="0">1. インポート時の設定ファイルを作成する</a></li><li><a href="#toc8" tabindex="0">2. インポート用のCloudFormationテンプレートを作成する</a></li><li><a href="#toc9" tabindex="0">3. インポート実行</a></li><li><a href="#toc10" tabindex="0">4. 削除してしまったその他のリソースを再作成する</a></li><li><a href="#toc11" tabindex="0">5. 復旧を確認</a></li></ol></li></ol></li><li><a href="#toc12" tabindex="0">おまけ + 参考文献</a><ol><li><a href="#toc13" tabindex="0">おまけ</a></li><li><a href="#toc14" tabindex="0">参考文献</a></li></ol></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">TL; DR;</span></h2>



<ol class="wp-block-list">
<li>CloudFormationの既存スタックへのリソースインポート機能を使う
<ul class="wp-block-list">
<li>スタック管理下から外れたサブネットを既存スタックにインポートします</li>
</ul>
</li>



<li>本来デプロイするはずのテンプレートを使用して既存スタックを再度更新します
<ul class="wp-block-list">
<li>消失したリソースがある場合はこの手順も踏んだほうが良いです</li>
</ul>
</li>
</ol>



<p></p>



<p>では、いきましょう。</p>



<h2 class="wp-block-heading"><span id="toc2">構成図とCloudFormationテンプレート</span></h2>



<p>今回想定している構成です。</p>



<p>ポイントはサブネットの存在に依存するリソースが存在していること、つまりサブネット上にVPC Lambdaがデプロイされている点になります。</p>



<p>また、ネットワーク復旧確認用にVPC EndpointとS3を用意しています。</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="943" height="471" src="https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-1.png" alt="" class="wp-image-193" srcset="https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-1.png 943w, https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-1-300x150.png 300w, https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-1-768x384.png 768w" sizes="(max-width: 943px) 100vw, 943px" /></figure>



<h2 class="wp-block-heading"><span id="toc3">本題</span></h2>



<p>まずは、今回使用するCloudFormationテンプレートを確認します。</p>



<p>テンプレートはNetwork.yamlとLambda.yamlの2種類です。</p>



<p>Network.yamlでは</p>



<ul class="wp-block-list">
<li>VPC</li>



<li>ルートテーブル</li>



<li>サブネット</li>



<li>VPCエンドポイント(Gateway)</li>



<li>パラメータストア(VPCID)　　　　　　← Lambda.yamlにパラメータを渡すために作成</li>



<li>パラメータストア(サブネットID)　　　← Lambda.yamlにパラメータを渡すために作成</li>
</ul>



<p>これらを作成しています。</p>



<p>また、Lambda.yamlでは、</p>



<ul class="wp-block-list">
<li>IAMロール</li>



<li>セキュリティグループ</li>



<li>Lambda関数</li>



<li>S3バケット　　　　　　　　　　　　　←ネットワーク疎通確認用</li>
</ul>



<p>これらを作成しています。</p>



<p>ネットワークの疎通が取れているかを確認するために、Lambda関数内ではS3バケットにファイルをはき出すロジックを入れています。</p>



<p>①正常時、②サブネットをスタックに引き戻したとき</p>



<p>これら2つのケースでネットワークに問題がないことを確認します。</p>



<p>詳細は以下のテンプレートを確認してください。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain" data-file="Network.yaml" data-show-lang="0"><code>AWSTemplateFormatVersion: &quot;2010-09-09&quot;
Description: Network

Parameters:
  Prefix:
    Description: Enter Prefix.
    Type: String
    Default: sample

Resources:
#---------------------------------------------------#
# VPC
#---------------------------------------------------#
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-vpc

#---------------------------------------------------#
# RouteTable
#---------------------------------------------------#
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-private-rt

#---------------------------------------------------#
# Subnet1A and RouteTableAssociation
#---------------------------------------------------#
  PrivateSubnet1A:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.16.0/20
      AvailabilityZone: !Select
        - 0
        - Fn::GetAZs: !Ref AWS::Region
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-private-subnet-1a

  PrivateSubnet1ARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable
      SubnetId: !Ref PrivateSubnet1A

#---------------------------------------------------#
# S3 VPCEndpoint
#---------------------------------------------------#
  RecoveryS3VPCEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: !Sub com.amazonaws.${AWS::Region}.s3
      VpcEndpointType: Gateway
      VpcId: !Ref VPC
      RouteTableIds:
        - !Ref PrivateRouteTable

#---------------------------------------------------#
# Parameters Section
#---------------------------------------------------#
  ParamRecoveryVPCId:
    Type: AWS::SSM::Parameter
    Properties:
      Name: /Network/VPCId
      Type: String
      Value: !Ref VPC

  ParamPrivateSubnet1AId:
    Type: AWS::SSM::Parameter
    Properties:
      Name: /Network/PrivateSubnet1AId
      Type: String
      Value: !Ref PrivateSubnet1A</code></pre></div>



<p></p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain" data-file="Lambda.yaml"><code>AWSTemplateFormatVersion: &quot;2010-09-09&quot;
Description: Lambda

Parameters:
  Prefix:
    Description: Enter Prefix.
    Type: String
    Default: sample

  ParamVPCId:
    Type: AWS::SSM::Parameter::Value&lt;String&gt;
    Default: /Network/VPCId

  ParamPrivate1ASubnetId:
    Type: AWS::SSM::Parameter::Value&lt;String&gt;
    Default: /Network/PrivateSubnet1AId

Resources:
#---------------------------------------------------#
# IAM Role for Lambda Function
#---------------------------------------------------#
  LambdaFunctionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub ${Prefix}-lambda-role
      AssumeRolePolicyDocument:
        Version: &quot;2012-10-17&quot;
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - &#39;sts:AssumeRole&#39;
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
        - arn:aws:iam::aws:policy/AmazonS3FullAccess

#---------------------------------------------------#
# SecurityGroup for Lambda Function
#---------------------------------------------------#
  LambdaSG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: for lambda
      GroupName: !Sub ${Prefix}-lambda-sg
      VpcId: !Ref ParamVPCId

  LambdaSGEgressToANY:
    Type: AWS::EC2::SecurityGroupEgress
    Properties:
         IpProtocol: -1
         CidrIp: 0.0.0.0/0
         GroupId: !Ref LambdaSG

#---------------------------------------------------#
# Lambda Function
#---------------------------------------------------#
  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Sub ${Prefix}-function
      Role: !GetAtt LambdaFunctionRole.Arn
      Runtime: python3.10
      Handler: index.lambda_handler
      Code:
        ZipFile: |
          import json
          import boto3
          from datetime import datetime
          
          def lambda_handler(event, context):
              s3 = boto3.client(&#39;s3&#39;)
              bucket_name = &#39;sample-check-network-status-bucket&#39;
              current_time = datetime.now().strftime(&#39;%Y%m%d_%H%M%S&#39;)
              file_name = f&#39;trial_{current_time}.txt&#39;
              file_content = &#39;Hello World!&#39;
              
              s3.put_object(Bucket=bucket_name, Key=file_name, Body=file_content)
              return {
                  &#39;statusCode&#39;: 200,
                  &#39;body&#39;: json.dumps(&#39;Hello from Lambda!&#39;)
              }
      VpcConfig:
        SecurityGroupIds:
          - !Ref LambdaSG
        SubnetIds:
          - !Ref ParamPrivate1ASubnetId
      Timeout: 10

#---------------------------------------------------#
# S3 Bucket for checking the network status
#---------------------------------------------------#
  AssetsBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub ${Prefix}-check-network-status-bucket
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      VersioningConfiguration:
        Status: Suspended</code></pre></div>



<h3 class="wp-block-heading"><span id="toc4">状況を再現(スタックを作成する)</span></h3>



<p>ここでは、CloudShellからスタック作成を実施します。</p>



<p>(破壊した環境にもよりますが、おそらく通常では開発環境等で復旧テスト→当該環境で復旧作業となると想定しています。その際に余計な環境差を出さないためにもCloudShellからの実行としています。)</p>



<p>2つのスタックには依存関係があるので、Network.yaml → Lambda.yamlの順でデプロイしていきます。</p>



<p>ファイルをCloudShellにアップロードした後、以下のコマンドを実行します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code># 1. ネットワークスタックを作成
aws cloudformation deploy \
    --template-file Network.yaml \
    --stack-name  sample-network-stack

# 2. Lambdaスタックを作成
aws cloudformation deploy \
    --template-file Lambda.yaml \
    --stack-name  sample-lambda-stack \
    --capabilities CAPABILITY_NAMED_IAM

# 3. ネットワークスタックの管理下にあるリソースを確認
aws cloudformation describe-stack-resources \
    --stack-name sample-network-stack \
    --query &quot;join(&#39;, &#39;, StackResources[*].LogicalResourceId)&quot; --output text \
    --output text</code></pre></div>



<p>3の実行結果は以下となります。</p>



<figure class="wp-block-image size-large is-resized"><img decoding="async" width="1024" height="19" src="https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-02-1024x19.png" alt="" class="wp-image-204" style="width:1078px;height:auto" srcset="https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-02-1024x19.png 1024w, https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-02-300x6.png 300w, https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-02-768x14.png 768w, https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-02.png 1062w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<p>また、Lambda関数を実行してS3バケットにファイルが出力できることも確認しておきます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code># 1. S3バケット内のオブジェクト数を確認
#    → 「Total Objects: 0」と表示される
aws s3 ls s3://sample-check-network-status-bucket \
    --recursive \
    --summarize

# 2. Lambda関数を実行
aws lambda invoke \
    --function-name sample-function \
    outputfile.txt

# 3. S3バケット内のオブジェクト数を確認
#    → 「Total Objects: 1」と表示される
aws s3 ls s3://sample-check-network-status-bucket\
    --recursive \
    --summarize</code></pre></div>



<p>ここまででスタックの作成はOKです。</p>



<p></p>



<h3 class="wp-block-heading"><span id="toc5">状況を再現(スタック管理下からサブネットを外す)</span></h3>



<p>では、ここからはサブネットをスタック管理下から外していきます。</p>



<p>Network.yaml内の記述を修正したDestructive-Network.yamlを用意して、既存のスタックを上書きしましょう。</p>



<p>Destructive-Network.yamlで作成予定のリソースは以下です。</p>



<ul class="wp-block-list">
<li>VPC</li>



<li>ルートテーブル</li>
</ul>



<p>サブネットを削除しているのがポイントです。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain" data-file="Destructive-Network.yaml"><code>AWSTemplateFormatVersion: &quot;2010-09-09&quot;
Description: Network

Parameters:
  Prefix:
    Description: Enter Prefix.
    Type: String
    Default: sample

Resources:
#---------------------------------------------------#
# VPC
#---------------------------------------------------#
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-vpc

#---------------------------------------------------#
# RouteTable
#---------------------------------------------------#
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-private-rt</code></pre></div>



<p></p>



<p>ではこのテンプレートをCloudShellにアップロードして、デプロイしちゃいます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash" data-show-lang="1"><code>aws cloudformation deploy \
    --template-file Destructive-Network.yaml \
    --stack-name  sample-network-stack</code></pre></div>



<p></p>



<p>1時間程度経過した後にコンソール画面を確認すると、サブネットの依存関係の問題で数回DELETE_FAILEDとなり、最終的にエラー文は出力されていはいるもののUPDATE_COMPLETEステータスとなっています。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code># DELETE_FAILED時のエラー文
Resource handler returned message: &quot;The subnet &#39;subnet-0dxxxxxxxxxxxxx25&#39; has dependencies and cannot be deleted.

# UPDATE_COMPLETE時の文
Update successful. One or more resources could not be deleted.</code></pre></div>



<p></p>



<p>ここで、ネットワークスタック管理下にあったサブネットの状況を確認します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code># 1. ネットワークスタックの管理下にあるリソースを確認
aws cloudformation describe-stack-resources \
    --stack-name sample-network-stack \
    --query &quot;join(&#39;, &#39;, StackResources[*].LogicalResourceId)&quot; --output text \
    --output text

# 2. Nameを指定して、既存のサブネットから対象のサブネットの情報を表示
aws ec2 describe-subnets \
    --query &quot;Subnets[?Tags[?Key==&#39;Name&#39; && Value==&#39;sample-private-subnet-1a&#39;]]&quot;</code></pre></div>



<p>1の実行結果は以下です。</p>



<figure class="wp-block-image size-full"><img decoding="async" width="298" height="14" src="https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-03.png" alt="" class="wp-image-207"/></figure>



<p>サブネットはスタック管理下にはいないことが伺えます。</p>



<p>また一方で、2の結果からはサブネットは削除されず環境上に存在しており、たんにスタックから外れているだけだと推測できます。</p>



<p>この状況ですと、今後の運用でNetwork.yamlテンプレート内でサブネットのID等を利用したい場合には値をべた書きすることになります。仮にこのテンプレートを1環境だけで利用する場合はこれでも構いませんが、複数環境で利用している場合には!Ref等で値を渡せないのはかなり厳しいです。</p>



<p></p>



<p>そのため、再度サブネットをスタック管理下に引き戻し、元の状態へ戻す必要がでてくるはずです。</p>



<h3 class="wp-block-heading"><span id="toc6">本題：サブネットをスタック管理下に引き戻す</span></h3>



<p>サブネットをスタック管理下に引き戻すためには、既存スタック(sample-network-stack)に対象のサブネットをインポートします。</p>



<p>既存スタックへのインポート機能の詳細については、以下の公式ドキュメントを参照ください。</p>



<p><a href="https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/resource-import-existing-stack.html#resource-import-existing-stack-cli">スタックへの既存リソースのインポート &#8211; AWS CloudFormation (amazon.com)</a></p>



<p></p>



<p>では、順を追ってインポートを実行していきましょう。</p>



<h4 class="wp-block-heading"><span id="toc7">1. インポート時の設定ファイルを作成する</span></h4>



<p>インポートコマンド実行時に必要になる設定ファイルを組み立てていきます。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain" data-file="Import.json"><code>[
    {
        &quot;ResourceType&quot;: &quot;AWS::EC2::Subnet&quot;,
        &quot;LogicalResourceId&quot;: &quot;PrivateSubnet1A&quot;,
        &quot;ResourceIdentifier&quot;: {
            &quot;SubnetId&quot;:&quot;subnet-0dxxxxxxxxxxxxx25&quot;
        }
    }
]</code></pre></div>



<p>各種キーについて簡単に説明します。</p>



<ul class="wp-block-list">
<li>ResourceType
<ul class="wp-block-list">
<li>インポート対象のリソースタイプを指定する</li>
</ul>
</li>



<li>LogicalResourceId
<ul class="wp-block-list">
<li>インポート時に読み込ませるCloudFormationテンプレート上で、インポート対象のリソースに割り当てる論理IDを指定する</li>



<li>この記事では最終的に元のNetwork.yamlで上書きするため、そこで使用していた論理IDを使っています</li>
</ul>
</li>



<li>ResourceIdentifier
<ul class="wp-block-list">
<li>インポート対象リソース識別子を指定する</li>
</ul>
</li>
</ul>



<p>また、それぞれのキーに何を指定すればよいか(ここではSubnetId)は、get-tempate-summeryコマンドで大まかに調査可能です。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code>aws cloudformation get-template-summary \
    --template-body file://./Network.yaml \
    --query &#39;ResourceIdentifierSummaries&#39;</code></pre></div>



<p></p>



<h4 class="wp-block-heading"><span id="toc8">2. インポート用のCloudFormationテンプレートを作成する</span></h4>



<p>ここでは、現在デプロイされているCloudFormationテンプレートにインポート対象とするサブネットを追記したものを作成します。</p>



<p>次の2点に注意してください。</p>



<ul class="wp-block-list">
<li>既存リソースの設定値は変更しない</li>



<li>インポート対象のリソースには、DeletionPolicyとUpdateReplacePolicyを追記する</li>
</ul>



<p>※その他の削除済みリソースは後ほど再作成します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain" data-file="Import-Network.yaml" data-line="39-51"><code>AWSTemplateFormatVersion: &quot;2010-09-09&quot;
Description: Network

Parameters:
  Prefix:
    Description: Enter Prefix.
    Type: String
    Default: sample

Resources:
#---------------------------------------------------#
# VPC
#---------------------------------------------------#
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: true
      EnableDnsHostnames: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-vpc

#---------------------------------------------------#
# RouteTable
#---------------------------------------------------#
  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-private-rt

#---------------------------------------------------#
# Subnet1A
#---------------------------------------------------#
  PrivateSubnet1A:
    DeletionPolicy: Retain
    UpdateReplacePolicy: Retain
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.16.0/20
      AvailabilityZone: !Select
        - 0
        - Fn::GetAZs: !Ref AWS::Region
      Tags:
        - Key: Name
          Value: !Sub ${Prefix}-private-subnet-1a</code></pre></div>



<p></p>



<h4 class="wp-block-heading"><span id="toc9">3. インポート実行</span></h4>



<p>インポートは次の3ステップで実行していきます。</p>



<ol class="wp-block-list">
<li>変更セットの作成 (CLIコマンド：create-change-set)</li>



<li>変更セットの確認 (CLIコマンド：describe-change-set)</li>



<li>変更セットの実行 (CLIコマンド：execute-change-set)</li>
</ol>



<p>Import.jsonとImport-Network.yamlをCloudShellにアップロードし、コマンドを実行します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code># 1. 変更セットの作成
aws cloudformation create-change-set \
    --stack-name sample-network-stack --change-set-name sample-network-change-set \
    --change-set-type IMPORT \
    --resources-to-import file://Import.json \
    --template-body file://Import-Network.yaml

# 2. 変更セットの確認
aws cloudformation describe-change-set \
    --stack-name sample-network-stack \
    --change-set-name sample-network-change-set

# 3. 変更セットの実行
aws cloudformation execute-change-set \
    --stack-name sample-network-stack \
    --change-set-name sample-network-change-set</code></pre></div>



<p></p>



<p>サブネットが再びスタック管理下となったことを確認します。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code># ネットワークスタックの管理下にあるリソースを確認
aws cloudformation describe-stack-resources \
    --stack-name sample-network-stack \
    --query &quot;join(&#39;, &#39;, StackResources[*].LogicalResourceId)&quot; --output text \
    --output text</code></pre></div>



<p>無事に成功しました。</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="344" height="17" src="https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-04.png" alt="" class="wp-image-214" srcset="https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-04.png 344w, https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-04-300x15.png 300w, https://tech-itoya.com/wp-content/uploads/2024/10/danto-20241013-04-320x17.png 320w" sizes="(max-width: 344px) 100vw, 344px" /></figure>



<p></p>



<h4 class="wp-block-heading"><span id="toc10">4. 削除してしまったその他のリソースを再作成する</span></h4>



<p>ここまでくればあと一息です。</p>



<p>最初のスタックではVPCエンドポイントなども管理していましたが、Destructive-Network.yamlで上書きした際にこれらのリソースは削除されてしまいました。</p>



<p>ですので、最後の手順として元のNetwork.yamlでデプロイして元の状態に戻しましょう。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash"><code># 1. ネットワークスタックを作成
aws cloudformation deploy \
    --template-file Network.yaml \
    --stack-name  sample-network-stack

# 2. ネットワークスタックの管理下にあるリソースを確認
aws cloudformation describe-stack-resources \
    --stack-name sample-network-stack \
    --query &quot;join(&#39;, &#39;, StackResources[*].LogicalResourceId)&quot; --output text \
    --output text</code></pre></div>



<p></p>



<h4 class="wp-block-heading"><span id="toc11">5. 復旧を確認</span></h4>



<p>最後にLambda関数を実行して、ネットワークの疎通に問題がないことを確認して終わりとします。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code># 1. Lambda関数を実行
aws lambda invoke \
    --function-name sample-function \
    outputfile.txt

# 2. S3バケット内のオブジェクト数を確認
#    → 「Total Objects: 2」と表示される
aws s3 ls s3://sample-check-network-status-bucket\
    --recursive \
    --summarize</code></pre></div>



<h2 class="wp-block-heading"><span id="toc12">おまけ + 参考文献</span></h2>



<h3 class="wp-block-heading"><span id="toc13">おまけ</span></h3>



<p>VPC Lambdaを含むスタックを削除しようとすると、ENIが引っかかって削除に時間がかかることがあります。(私の環境では25分ほどかかりました。)</p>



<p>そんなときのTipsとして、先にCLI操作によってLambdaの設定からVPC設定を外してしまいましょう。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-bash" data-lang="Bash" data-show-lang="1"><code># 1. S3バケットを空にする
aws s3 rm s3://sample-check-network-status-bucket --recursive

# 2. Lambda関数からVPC設定を外す
aws lambda update-function-configuration \
    --function-name sample-function \
    --vpc-config SubnetIds=[],SecurityGroupIds=[]

# 3. Lambdaスタックを削除
aws cloudformation delete-stack \
    --stack-name sample-lambda-stack

# 4. ネットワークスタックを削除(※依存関係があるため、Lambdaスタックが削除されたことを確認してから実行)
aws cloudformation delete-stack \
    --stack-name sample-network-stack</code></pre></div>



<p>以上です。</p>



<h3 class="wp-block-heading"><span id="toc14">参考文献</span></h3>



<ul class="wp-block-list">
<li><a href="https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/resource-import-existing-stack.html#resource-import-existing-stack-cli">スタックへの既存リソースのインポート &#8211; AWS CloudFormation (amazon.com)</a>
<ul class="wp-block-list">
<li>公式ドキュメント</li>
</ul>
</li>



<li><a href="https://qiita.com/a_b_/items/466ba74cc28efcb11285">CloudFormationのresource importを試してみた #AWS &#8211; Qiita</a>
<ul class="wp-block-list">
<li>スタックへのインポートについて紹介しているQiita記事</li>
</ul>
</li>
</ul>
		<div class="wpulike wpulike-heart " ><div class="wp_ulike_general_class wp_ulike_is_restricted"><button type="button"
					aria-label="いいねボタン"
					data-ulike-id="134"
					data-ulike-nonce="16282860fa"
					data-ulike-type="post"
					data-ulike-template="wpulike-heart"
					data-ulike-display-likers=""
					data-ulike-likers-style="popover"
					class="wp_ulike_btn wp_ulike_put_image wp_post_btn_134"></button><span class="count-box wp_ulike_counter_up" data-ulike-counter-value="+1"></span>			</div></div>
	]]></content:encoded>
					
					<wfw:commentRss>https://tech-itoya.com/cloudformation%e3%82%b9%e3%82%bf%e3%83%83%e3%82%af%e3%81%8b%e3%82%89%e5%a4%96%e3%82%8c%e3%81%9f%e3%82%b5%e3%83%96%e3%83%8d%e3%83%83%e3%83%88%e3%82%92%e3%82%b9%e3%82%bf%e3%83%83%e3%82%af%e7%ae%a1/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Terragrunt + Tfcmt + Github Actions で Plan 結果の Summary を Github 上に表示したい</title>
		<link>https://tech-itoya.com/terragrunt-tfcmt-github-actions-%e3%81%a7-plan-%e7%b5%90%e6%9e%9c%e3%81%ae-summary-%e3%82%92-github-%e4%b8%8a%e3%81%ab%e8%a1%a8%e7%a4%ba%e3%81%97%e3%81%9f%e3%81%84/</link>
					<comments>https://tech-itoya.com/terragrunt-tfcmt-github-actions-%e3%81%a7-plan-%e7%b5%90%e6%9e%9c%e3%81%ae-summary-%e3%82%92-github-%e4%b8%8a%e3%81%ab%e8%a1%a8%e7%a4%ba%e3%81%97%e3%81%9f%e3%81%84/#respond</comments>
		
		<dc:creator><![CDATA[tori-dash]]></dc:creator>
		<pubDate>Thu, 19 Sep 2024 17:09:51 +0000</pubDate>
				<category><![CDATA[TECH記事]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[Github Actions]]></category>
		<category><![CDATA[IaC]]></category>
		<category><![CDATA[Terraform]]></category>
		<guid isPermaLink="false">https://tech-itoya.com/?p=32</guid>

					<description><![CDATA[こんにちは、とりです。 皆さんは Terraform リポジトリを管理する際、CIを組んでいるでしょうか？ プルリク公開時に Terraform Plan を自動実行して、結果をGithub上で確認できると、変更箇所が伝 [&#8230;]]]></description>
										<content:encoded><![CDATA[
<p>こんにちは、とりです。</p>



<p>皆さんは Terraform リポジトリを管理する際、CIを組んでいるでしょうか？</p>



<p>プルリク公開時に Terraform Plan を自動実行して、結果をGithub上で確認できると、変更箇所が伝わりやすくなりハッピーかと思います。</p>



<p>tfcmt はそんなTerraformのCIを組みたいときに活躍するツールで、terraform plan の結果を素敵な感じに整形して、プルリクにコメントしてくれます</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="909" height="485" data-id="121" src="https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-1.png" alt="" class="wp-image-121" srcset="https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-1.png 909w, https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-1-300x160.png 300w, https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-1-768x410.png 768w" sizes="(max-width: 909px) 100vw, 909px" /></figure>
</figure>



<p>ただ、Terragrunt のような複数の Statefile を管理するリポジトリの場合、プルリク上に大量にコメントが流れてしまい、かえって見づらくなることもあるかなと思います。</p>



<p>今回触っていた IaC リポジトリでも、6環境×モジュールが6~7個あったため、最大40個強のPlan結果がプルリクコメントに並んでしまうことになり、さすがにこれを運用すると辛そうだな&#8230;という印象でした。</p>



<p>↓ は Bot が PR に大量にコメントを流している様子です</p>



<figure class="wp-block-image size-full is-resized"><img loading="lazy" decoding="async" width="744" height="885" src="https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-2.png" alt="bot-tfcmt" class="wp-image-122" style="width:840px;height:auto" srcset="https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-2.png 744w, https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-2-252x300.png 252w" sizes="(max-width: 744px) 100vw, 744px" /></figure>



<p>そこで、Github Workflow の Summary に結果を見やすくまとめることで、上記の課題を解決しつつ、快適に terraform の CI を運用する方法を紹介したいと思います。</p>



<p>Terragrunt でなく生の Terraform を使っていても、Module 等で State の分割管理を行っている場合、同様の課題感を持っていたりすると思うので、そういった方の参考にもなれば幸いです。</p>



<p>ついでに Github Enterprise 環境で tfcmt を構築している記事もあまり見かけなかったので、その解説もできたらと思っています。</p>




  <div id="toc" class="toc tnt-number toc-center tnt-number border-element"><input type="checkbox" class="toc-checkbox" id="toc-checkbox-4" checked><label class="toc-title" for="toc-checkbox-4">目次</label>
    <div class="toc-content">
    <ol class="toc-list open"><li><a href="#toc1" tabindex="0">結論</a></li><li><a href="#toc2" tabindex="0">実装</a><ol><li><a href="#toc3" tabindex="0">全体のディレクトリ構成</a></li><li><a href="#toc4" tabindex="0">tfcmt.yml の設定</a></li><li><a href="#toc5" tabindex="0">tfwrapper.sh の設定</a></li><li><a href="#toc6" tabindex="0">Github Actions のワークフロー</a></li></ol></li><li><a href="#toc7" tabindex="0">改善ポイント</a></li></ol>
    </div>
  </div>

<h2 class="wp-block-heading"><span id="toc1">結論</span></h2>



<p>いろいろ試した結果、こんな形で落ち着きました。</p>



<figure class="wp-block-gallery has-nested-images columns-default is-cropped wp-block-gallery-2 is-layout-flex wp-block-gallery-is-layout-flex">
<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="489" data-id="124" src="https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-3-1024x489.png" alt="" class="wp-image-124" srcset="https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-3-1024x489.png 1024w, https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-3-300x143.png 300w, https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-3-768x366.png 768w, https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-3-1536x733.png 1536w, https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-3.png 1878w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>
</figure>



<p>環境ごとに Github Actions のワークフローを作成し、サマリー部分にモジュールごと実行した Plan 結果を見やすくまとめています。</p>



<p>逆にプルリクのラベル付けやコメントを書き込む機能などは、明示的に無効化を行いました。</p>



<h2 class="wp-block-heading"><span id="toc2">実装</span></h2>



<h3 class="wp-block-heading"><span id="toc3">全体のディレクトリ構成</span></h3>



<p>Terraform リポジトリのディレクトリ構成としてはこんな感じになっています</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>.
├── .github
│   ├── workflows
│   │   ├── develop_terraform_plan.yml
│   │   ├── staging_terraform_plan.yml
│   │   └── ... (other files)
│   └── tfcmt
│       ├── tfcmt.yml
│       └── tfwrapper.sh
├── environments
│   ├── develop
│   │   └── ap-northeast-1
│   │       └── ... (modules)
│   └── ... (other environments)
├── modules
│   ├── ecs
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── variables.tf
│   │   └── ... (other files)
│   └── ... (other modules)
└── terragrunt.hcl</code></pre></div>



<h3 class="wp-block-heading"><span id="toc4">tfcmt.yml の設定</span></h3>



<p>tfcmt.yml という名前で config ファイルを書いておき、tfcmt コマンド実行時にオプションに指定してあげることで、plan 結果を整形するフォーマットをカスタマイズできます。</p>



<p>かなりカスタマイズしやすく、個人的に感動したポイントでした。</p>



<p>参考: <a href="https://suzuki-shunsuke.github.io/tfcmt/config#default-configuration">https://suzuki-shunsuke.github.io/tfcmt/config#default-configuration</a></p>



<p>今回設定した tfcmt.yml の内容になります</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>embedded_var_names: []
terraform:
  plan:
    # github enterprise の時のみ設定が必要
    # 参考: https://suzuki-shunsuke.github.io/tfcmt/github-enterprise
    ghe_base_url: https://XXX.github.com
    ghe_graphql_endpoint: https://XXX.github.com/api/graphql
    # terragrunt だと statefile 毎ラベルが上書きされて逆に管理し辛かったので無効化
    disable_label: true
    template: |
      &lt;details&gt;&lt;summary&gt;&lt;strong&gt; :package: Target Module: {{.Vars.target}} &lt;/strong&gt;&lt;/summary&gt;
        {{template &quot;result&quot; .}}
        {{template &quot;updated_resources&quot; .}}
        {{template &quot;changed_result&quot; .}}
        {{template &quot;change_outside_terraform&quot; .}}
        {{template &quot;warning&quot; .}}
        {{template &quot;error_messages&quot; .}}
      &lt;/details&gt;</code></pre></div>



<p>ポイントとしては以下になります</p>



<ul class="wp-block-list">
<li>Github Enterprise の場合は plan 実行時の 環境変数に <code>ghe_base_url</code> と <code>ghe_graphql_endpoint</code> を足してあげる必要があります。</li>



<li>tfcmt は自動で plan 結果を要約するようなラベル付けを行ってくれるのですが、複数の Statefile を管理するリポジトリの場合は、逆に見辛さが勝ってしまったので無効化しています。</li>



<li><strong>template:</strong> に続くブロックで plan 結果をどんな感じで整形するか指定できるので、お好みでいじってみても良いかと思います。</li>
</ul>



<h3 class="wp-block-heading" id="tfwrapper.sh"><span id="toc5">tfwrapper.sh の設定</span></h3>



<p>Terragrunt の場合、tfcmt が <code>terragrunt run-all plan</code> コマンドをそのまま叩けないので wrapper のスクリプトをかませてあげる必要があります。</p>



<p>Terraform をそのまま使っている場合はこの作業は不要かなと思います。</p>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>#!/bin/bash
set -euo pipefail

# コマンドの種類を取得(例: apply, plan, fmt...)
type=$(echo &quot;$@&quot; |  awk &#39;{print $1}&#39;)

# カレントディレクトリを整形して、target の module 名を取得
base_dir=$(git rev-parse --show-toplevel)
target=${PWD#&quot;$base_dir&quot;/}
target=$(echo &quot;$target&quot; | sed &#39;s|/\.terragrunt-cache/.*||&#39;)

if [ &quot;$type&quot; == &quot;plan&quot; ]; then
    tfcmt --config &quot;$GITHUB_WORKSPACE/.github/tfcmt/tfcmt.yml&quot; \
          --output &quot;/tmp/tgplan.md&quot; \
          -var &quot;target:${target}&quot; \
          plan -patch -- terraform &quot;$@&quot;
fi</code></pre></div>



<p>ここが terragrunt run-all plan コマンドを実行した時の実体のスクリプトになるのですが、その際に　tfcmt の処理を噛ませてあげる流れとなっています</p>



<p>以下オプションの説明になります</p>



<ul class="wp-block-list">
<li><strong>&#8211;config オプション</strong>: plan 結果の整形フォーマットなどを config ファイルに書くことができます。上のステップで作成した tfcmt.yml ファイルを config として指定します</li>
</ul>



<ul class="wp-block-list">
<li><strong>&#8211;output オプション:</strong> tfcmt のメインの使い方は、プルリクのコメントに plan 結果を出力することだと思いますが、今回はそれを無効化しローカルにファイルに吐き出しています</li>
</ul>



<ul class="wp-block-list">
<li><strong>-var オプション:</strong> tfcmt に変数を渡すことができます。今回は terraform のモジュール名を渡して、tfcmt が Plan 結果を整形する際に、モジュール名も含めて出力できるようにしています</li>
</ul>



<p>細かい仕様は 公式 doc を参照ください。</p>



<p>参考: <a href="https://suzuki-shunsuke.github.io/tfcmt/terragrunt-run-all">https://suzuki-shunsuke.github.io/tfcmt/terragrunt-run-all</a></p>



<h3 class="wp-block-heading"><span id="toc6">Github Actions のワークフロー</span></h3>



<div class="hcb_wrap"><pre class="prism line-numbers lang-plain"><code>name: Execute terragrunt plan
on:
  pull_request:
    branches:
      - &#39;main&#39;
    paths:
      - &#39;environments/develop/**&#39;
      - &#39;modules/**&#39;
  workflow_dispatch:
env:
  Enviromnet: develop
  IAM_ROLE_ARN: XXX
jobs:
  plan-common:
    runs-on: XXX
    steps:
      - uses: actions/checkout@v3

     # plan 用の権限を assume する
      - name: assume ECS Role
 　　 　uses: aws-actions/configure-aws-credentials@v4
   　　　 with:
   　　　   aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
      　　　aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
      　　　aws-region: ap-northeast-1

      # tfcmt のインストール
      - name: setup tfcmt
        env:
          TFCMT_VERSION: v4.13.0
        run: |
          curl -L &quot;https://github.com/suzuki-shunsuke/tfcmt/releases/download/${TFCMT_VERSION}/tfcmt_linux_amd64.tar.gz&quot; -o /tmp/tfcmt.tar.gz
          tar xzf /tmp/tfcmt.tar.gz -C /tmp
          mv /tmp/tfcmt /usr/local/bin
          tfcmt --version

      # terragrunt plan の実行
      - name: Excute terragrunt plan
        shell: bash
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          cd ./environments/${{ env.Enviromnet }}
          terragrunt run-all init
          chmod a+x $GITHUB_WORKSPACE/.github/tfcmt/tfwrapper.sh
          terragrunt run-all plan -no-color -input=false --terragrunt-tfpath $GITHUB_WORKSPACE/.github/tfcmt/tfwrapper.sh

      # actions の workflow summary に plan 結果の summary を通知する
      - name: Create Job Summary
        if: always()
        uses: actions/github-script@v6
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const fs = require(&#39;fs&#39;);
            const filePath = &#39;/tmp/tgplan.md&#39;;
            const output = fs.readFileSync(filePath, &#39;utf8&#39;);
            await core.summary
              .addHeading(&#39;Terragrunt plan report&#39;)
              .addRaw(output)
              .write();</code></pre></div>



<p>まず terragrunt plan を実行するための IAM 権限の Assume Role と、tfcmt をローカルにインストール処理を実行後、メインとなる terragrunt run-all plan を実行しています。</p>



<p><a href="#tfwrapper.sh">ここで補足した通り</a>、tfcmt が直接 terragrunt run-all plan を実行することができないので、<code>--terragrunt-tfpath</code> オプションで wrapper スクリプトを呼び出し、そのスクリプト内で tfcmt を実行させています。</p>



<p>その後、<code>actions/github-script@v6</code> を使い、tfcmt が吐き出した md ファイルを Github の Summary 部分に書き込む処理を入れています。</p>



<p>結果、こんな感じで Summary に環境ごとの Plan 結果をまとめることができました！</p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="489" src="https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-3-1-1024x489.png" alt="" class="wp-image-125" srcset="https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-3-1-1024x489.png 1024w, https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-3-1-300x143.png 300w, https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-3-1-768x366.png 768w, https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-3-1-1536x733.png 1536w, https://tech-itoya.com/wp-content/uploads/2024/09/tori-20240920-3-1.png 1878w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading"><span id="toc7">改善ポイント</span></h2>



<p>使い勝手自体の改善は満足したので、このまま他のいろんなIaCリポジトリに横展開していきたいと考えています。</p>



<p>横展開する際、毎回 tfcmt の Config やらを設定するのは面倒なので、共通部品化して Actions モジュールを他の IaC リポジトリに配布できるといいなと考えています。機会があればチャレンジしてみようと思います。</p>



<p>tfcmt のおかげで Terraform の CI 体験が大分改善しそうで楽しみです！</p>



<p></p>
		<div class="wpulike wpulike-heart " ><div class="wp_ulike_general_class wp_ulike_is_restricted"><button type="button"
					aria-label="いいねボタン"
					data-ulike-id="32"
					data-ulike-nonce="54b7d9c3c7"
					data-ulike-type="post"
					data-ulike-template="wpulike-heart"
					data-ulike-display-likers=""
					data-ulike-likers-style="popover"
					class="wp_ulike_btn wp_ulike_put_image wp_post_btn_32"></button><span class="count-box wp_ulike_counter_up" data-ulike-counter-value="+1"></span>			</div></div>
	]]></content:encoded>
					
					<wfw:commentRss>https://tech-itoya.com/terragrunt-tfcmt-github-actions-%e3%81%a7-plan-%e7%b5%90%e6%9e%9c%e3%81%ae-summary-%e3%82%92-github-%e4%b8%8a%e3%81%ab%e8%a1%a8%e7%a4%ba%e3%81%97%e3%81%9f%e3%81%84/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
