情報畑でつかまえてロゴ
本サイトは NTTテクノクロスが旬の IT をキーワードに
IT 部門が今知っておきたい最新テクノロジーに関する情報をお届けするサイトです

Terraform連載 第2回:Terraform v1.5の紹介&活用方法について考えてみた

前回の「デジタルツイン」×「ネットワーク」事業部コラボ企画で話題にあげたTerraformについて、今回はv1.5の新機能をご紹介します。

terraform_banner.pngのサムネイル画像

はじめに

こんにちは、NTTテクノクロスの岩瀨です。

先日、Terraformのバージョン1.5がリリースされました。
その中にとても面白いなと感じた機能がありますので、本日はこれを紹介&活用方法について考えてみたいと思います。

Terraformの概要については前回の記事で紹介していますので、気になる方はこちらもチェックしてみてください。

関連記事:
Terraform連載 第1回:いまさら聞けない、IaCってなに?~Terraform、IaSQLの紹介~
「Terraform連載 第2回:Terraform v1.5の紹介&活用方法について考えてみた」
Terraform連載 第3回:Terraform v1.6のtestコマンドについてご紹介
Terraform連載 第4回:for_eachの使い方
Terraform連載 第5回:module(モジュール)の紹介
Terraform連載 第6回:Terraform v1.7 removedブロックの紹介
Terraform連載 第7回:importブロックはmoduleのリソースも取り込めるか?
Terraform連載 第8回:Terraformの管理スコープとしてiDRACをimportしてみた

Terraform v1.5で追加されたimportブロックについて

ここからはTerraform v1.5で追加されたimportブロックと、-generate-config-outオプションについて取り上げてみます。

簡単に書いてしまうと、この新機能を使うことで「AWSなどの既存のリソースからのTerraformコード生成と、Terraform管理への取り込みが簡単にできる」ようになります。
クラウド上のインフラストラクチャーを手動操作で管理している場合でも、この機能を使うことで容易にIaC化できる可能性もあるわけです。
(v1.5より前は、手動でTerraformコードを書き、リソースを1つずつterraform state importコマンドでTerraform管理に取り込む必要がありました)

今回は、AWS環境に手動で作成したリソースをTerraform管理に取り込む方法を検証してみました。

準備1:AWS環境に手動でリソースを作る

事前に以下のリソースを作成しました。

  • ・VPC1つ
  • ・サブネット4つ(ap-northeast-1aと1cに、Public用とPrivate用の1つずつ)
  • ・ルートテーブル4つ(各サブネット用に1つずつ)

上記のうちVPCとサブネットをTerraform管理に加えてみようと思います。
(ルートテーブルは「importブロックで指定しなかった場合の挙動」を確認するために追加したもので、Terraform管理にはしません)

ちなみにAWSの画面では以下のように見えています。

[VPC]

[Subnet]

[RouteTable]

VPCにはnaraというNameタグが付いていて、サブネットやルートテーブルはnara + リソース種別 + AZ、という形でNameタグをつけています。
(ちなみにnaraという名称には意味はありません)

準備2:Terraform環境を準備する

今回は検証のため、terraformコマンドの実行環境はCloudShellとし、tfstateファイルはS3で管理します。

使用するterraformコマンドのバージョンはv1.5.3とします(執筆時点(2023/7/25)の最新版です)。terraformコマンドのインストール方法は公式ドキュメントを参照ください。

providerブロックおよびterraformブロックは以下のように構成します(今回はprovider.tfファイルとします)。

[terraform:provider.tf]

provider "aws" {
 region = "ap-northeast-1"
}
terraform {
 backend "s3" {
  bucket = "terraform-state-management-tx"
  key    = "tf-v15test-01"
  region = "ap-northeast-1"
 }
 required_providers {
  aws = {
   source  = "hashicorp/aws"
   version = "~> 5.0"
  }
 }
}

動作確認としてこの状態で一度terraform initおよびterraform planを実行して正常終了する事を確認しておくと、この後の操作でエラーになってしまった際の切り分けがやりやすくなるかもしれません。

importブロックをファイルに記述する 

ここからが本題です。

importブロックの使い方は、Terraformの公式ドキュメントに説明があります。
公式ドキュメント (import)
公式ドキュメント (import - Generating configuration)

importブロックとresourceブロックが1対1の関係になり、importブロックのto=xxxで論理名、id=yyyで対象リソースのIDを指定します。

手動作成したVPCとサブネットをTerraform管理に取り込む場合は、以下のようにimportブロックを記載します(今回はimport-vpc-subnet.tfファイルとします)。

[terraform:import-vpc-subnet.tf]

# vpc "nara" import                 
import {
 id = "vpc-0d22e95db506de346"
 to = aws_vpc.nara
}
 
# subnet "nara-private-1a" import
import {
 id = "subnet-03c0e41a059737435"
 to = aws_subnet.nara_private_1a
}
 
# subnet "nara-private-1c" import
import {
 id = "subnet-0a8f9dbeabb8c6076"
 to = aws_subnet.nara_private_1c
}
 
# subnet "nara-public-1a" import
import {
 id = "subnet-0ce0792423ad2c7c5"
 to = aws_subnet.nara_public_1a
}
 
# subnet "nara-publi-1c" import
import {
 id = "subnet-0a9b87daaf8c81b23"
 to = aws_subnet.nara_public_1c
}

terraform planでコードを生成する

ではimportブロックで実際にコードを生成してみましょう。

terraform initでAWS providerを構成した後、以下のコマンドでコードを生成します。

[bash]

terraform plan -generate-config-out=vpc-subnet.tf

-generate-config-out=vpc-subnet.tfは出力先のファイルを指定するオプションです。今回は「vpc-subnet.tf」に保存していますが、任意のファイル名で構いません。

実行結果は以下です。今回の検証では、以下のようなエラーが発生し、正常終了しませんでした。

  • ・Conflicting configuration arguments
  • ・enable_lni_at_device_index must not be zero, got 0
  • ・Missing required argument

[bash]

$ terraform plan -generate-config-out=vpc-subnet2.tf
aws_subnet.nara_public_1a: Preparing import... [id=subnet-0ce0792423ad2c7c5]
aws_subnet.nara_private_1a: Preparing import... [id=subnet-03c0e41a059737435]
aws_subnet.nara_private_1c: Preparing import... [id=subnet-0a8f9dbeabb8c6076]
aws_vpc.nara: Preparing import... [id=vpc-0d22e95db506de346]
aws_subnet.nara_public_1c: Preparing import... [id=subnet-0a9b87daaf8c81b23]
aws_subnet.nara_public_1a: Refreshing state... [id=subnet-0ce0792423ad2c7c5]
aws_subnet.nara_public_1c: Refreshing state... [id=subnet-0a9b87daaf8c81b23]
aws_subnet.nara_private_1a: Refreshing state... [id=subnet-03c0e41a059737435]
aws_subnet.nara_private_1c: Refreshing state... [id=subnet-0a8f9dbeabb8c6076]
aws_vpc.nara: Refreshing state... [id=vpc-0d22e95db506de346] Planning failed. Terraform encountered an error while generating this plan. ╷
│ Warning: Config generation is experimental

│ Generating configuration during import is currently experimental, and the generated configuration format may change in future versions.


│ Error: Conflicting configuration arguments

│ with aws_subnet.nara_private_1c,
│ on vpc-subnet2.tf line 2:
│ (source code not available)

│ "availability_zone": conflicts with availability_zone_id
╵ (以下エラーが続くため省略)

これらエラーが発生してしまう原因については、公式ドキュメント (Conflicting resource arguments)にも記載がある制限事項と思われます。コードはリソースの属性値を元に生成されますが、この際に「どちらか片方しか指定できない」ような属性値を両方ともコードに含めてしまうようです。

なお今回のエラーは「生成したコードの整合性チェックでエラーになった」だけのようで、コードの生成自体は成功していました。作成されたコードは以下です。

[terraform:vpc-subnet.tf]

# __generated__ by Terraform
# Please review these resources and move them into your main configuration files.
 
# __generated__ by Terraform
resource "aws_subnet" "nara_private_1c" {
 assign_ipv6_address_on_creation                = false
 availability_zone                              = "ap-northeast-1c"
 availability_zone_id                           = "apne1-az1"
 cidr_block                                     = "172.16.0.192/26"
 customer_owned_ipv4_pool                       = null
 enable_dns64                                   = false
 enable_lni_at_device_index                     = 0
 enable_resource_name_dns_a_record_on_launch    = false
 enable_resource_name_dns_aaaa_record_on_launch = false
 ipv6_cidr_block                                = null
 ipv6_native                                    = false
 map_customer_owned_ip_on_launch                = false
 map_public_ip_on_launch                        = false
 outpost_arn                                    = null
 private_dns_hostname_type_on_launch            = "ip-name"
 tags = {
  Name = "nara-private-1c"
  Test = "TestTag"
 }
 tags_all = {
  Name = "nara-private-1c"
  Test = "TestTag"
 }
 vpc_id = "vpc-0d22e95db506de346"
}
 
# __generated__ by Terraform
resource "aws_subnet" "nara_private_1a" {
 (似た内容のため省略)
}
 
# __generated__ by Terraform
resource "aws_subnet" "nara_public_1c" {
 (似た内容のため省略)
}
 
# __generated__ by Terraform
resource "aws_subnet" "nara_public_1a" {
 (似た内容のため省略)
}
 
# __generated__ by Terraform
resource "aws_vpc" "nara" {
 assign_generated_ipv6_cidr_block     = false
 cidr_block                           = "172.16.0.0/16"
 enable_dns_hostnames                 = false
 enable_dns_support                   = true
 enable_network_address_usage_metrics = false
 instance_tenancy                     = "default"
 ipv4_ipam_pool_id                    = null
 ipv4_netmask_length                  = null
 ipv6_cidr_block                      = null
 ipv6_cidr_block_network_border_group = null
 ipv6_ipam_pool_id                    = null
 ipv6_netmask_length                  = 0
 tags = {
  Name = "nara"
  Test = "TestTag"
 }
 tags_all = {
  Name = "nara"
  Test = "TestTag"
 }
}

コードを見てみると、エラーメッセージでもあった通りavailability_zoneavailability_zone_idという「どちらか片方だけ指定できる」ものが両方含まれており、有効なTerraformのコードではないことが分かります。
それ以外でもaws_subnetのresourceブロックで指定しているvpc_idも固定文字列になってしまっていますね。

このままでは使えませんので、手動で手直しをしてあげます。

生成したコードを手動で手直しする

今回は以下の方針でコードを修正しました。

  • ・vpc_idなど別リソースのIDを指定する箇所はIDの固定文字列から論理名に変更する
  • ・デフォルト値で構わない設定は削除する

手直し後のvpc-subnet.tfは以下のようになりました。とてもスッキリしましたね。

[terraform:vpc-subnet.tf]

resource "aws_subnet" "nara_private_1a" {
 vpc_id            = aws_vpc.nara.id
 availability_zone = "ap-northeast-1a"
 cidr_block        = "172.16.0.64/26"
 tags = {
  Name = "nara-private-1a"
 }
}
 
resource "aws_subnet" "nara_public_1a" {
 (似た内容のため省略)
}
 
resource "aws_subnet" "nara_public_1c" {
 (似た内容のため省略)
}
 
resource "aws_subnet" "nara_private_1c" {
 (似た内容のため省略)
}
 
resource "aws_vpc" "nara" {
 cidr_block                           = "172.16.0.0/16"
 enable_dns_hostnames                 = false
 enable_dns_support                   = true
 enable_network_address_usage_metrics = false
 instance_tenancy                     = "default"
 tags = {
  Name = "nara"
 }
}

このコードでterraform planを実行してみます。

[bash]

$ terraform plan 
 
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
 + create
 
Terraform will perform the following actions:
 
 # aws_subnet.nara_private_1a will be created
 + resource "aws_subnet" "nara_private_1a" {
   + arn                                            = (known after apply)
   + assign_ipv6_address_on_creation                = false
   + availability_zone                              = "ap-northeast-1a"
   + availability_zone_id                           = (known after apply)
   + cidr_block                                     = "172.16.0.64/26"
   + enable_dns64                                   = false
   + enable_resource_name_dns_a_record_on_launch    = false
   + enable_resource_name_dns_aaaa_record_on_launch = false
   + id                                             = (known after apply)
   + ipv6_cidr_block_association_id                 = (known after apply)
   + ipv6_native                                    = false
   + map_public_ip_on_launch                        = false
   + owner_id                                       = (known after apply)
   + private_dns_hostname_type_on_launch            = (known after apply)
   + tags                                           = {
     + "Name" = "nara-private-1a"
    }
   + tags_all                                       = {
     + "Name" = "nara-private-1a"
    }
   + vpc_id                                         = (known after apply)
  }
 
 # aws_subnet.nara_private_1c will be created
 + resource "aws_subnet" "nara_private_1c" {
   (似た内容のため省略)
  }
 
 # aws_subnet.nara_public_1a will be created
 + resource "aws_subnet" "nara_public_1a" {
   (似た内容のため省略)
  }
 
 # aws_subnet.nara_public_1c will be created
 + resource "aws_subnet" "nara_public_1c" {
   (似た内容のため省略)
  }
 
 # aws_vpc.nara will be created
 + resource "aws_vpc" "nara" {
   + arn                                  = (known after apply)
   + cidr_block                           = "172.16.0.0/16"
   + default_network_acl_id               = (known after apply)
   + default_route_table_id               = (known after apply)
   + default_security_group_id            = (known after apply)
   + dhcp_options_id                      = (known after apply)
   + enable_dns_hostnames                 = false
   + enable_dns_support                   = true
   + enable_network_address_usage_metrics = false
   + id                                   = (known after apply)
   + instance_tenancy                     = "default"
   + ipv6_association_id                  = (known after apply)
   + ipv6_cidr_block                      = (known after apply)
   + ipv6_cidr_block_network_border_group = (known after apply)
   + main_route_table_id                  = (known after apply)
   + owner_id                             = (known after apply)
   + tags                                 = {
     + "Name" = "nara"
    }
   + tags_all                             = {
     + "Name" = "nara"
    }
  }
 
Plan: 5 to add, 0 to change, 0 to destroy.
 
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

実行計画が作成されましたので、有効なterraformコードになっていそうです。

これでTerraform管理に取り込む準備が整いました。

terraform applyでTerraform管理に取り込む

ではリソースをterraform管理に取り込んでみましょう。terraform applyで取り込むことができます。

$ terraform apply
aws_vpc.nara: Preparing import... [id=vpc-0d22e95db506de346]
aws_vpc.nara: Refreshing state... [id=vpc-0d22e95db506de346]
aws_subnet.nara_private_1a: Preparing import... [id=subnet-03c0e41a059737435]
aws_subnet.nara_public_1a: Preparing import... [id=subnet-0ce0792423ad2c7c5]
aws_subnet.nara_public_1c: Preparing import... [id=subnet-0a9b87daaf8c81b23]
aws_subnet.nara_private_1c: Preparing import... [id=subnet-0a8f9dbeabb8c6076]
aws_subnet.nara_private_1a: Refreshing state... [id=subnet-03c0e41a059737435]
aws_subnet.nara_public_1a: Refreshing state... [id=subnet-0ce0792423ad2c7c5]
aws_subnet.nara_private_1c: Refreshing state... [id=subnet-0a8f9dbeabb8c6076]
aws_subnet.nara_public_1c: Refreshing state... [id=subnet-0a9b87daaf8c81b23]
 
Terraform will perform the following actions:
 
(terraform plan実行時と同じ内容のため省略)
 
Plan: 5 to import, 0 to add, 0 to change, 0 to destroy.
 
Do you want to perform these actions?
 Terraform will perform the actions described above.
 Only 'yes' will be accepted to approve.
 
 Enter a value: 

Plan: 5 to importとなっており、5つのリソースをインポートする(Terraform管理に取り込む)よ、と表示されていますね。

問題ないのでyesを入力します。

Enter a value: yes 
 
aws_vpc.nara: Importing... [id=vpc-0d22e95db506de346]
aws_vpc.nara: Import complete [id=vpc-0d22e95db506de346]
aws_subnet.nara_public_1a: Importing... [id=subnet-0ce0792423ad2c7c5]
aws_subnet.nara_public_1a: Import complete [id=subnet-0ce0792423ad2c7c5]
aws_subnet.nara_private_1a: Importing... [id=subnet-03c0e41a059737435]
aws_subnet.nara_private_1a: Import complete [id=subnet-03c0e41a059737435]
aws_subnet.nara_public_1c: Importing... [id=subnet-0a9b87daaf8c81b23]
aws_subnet.nara_public_1c: Import complete [id=subnet-0a9b87daaf8c81b23]
aws_subnet.nara_private_1c: Importing... [id=subnet-0a8f9dbeabb8c6076]
aws_subnet.nara_private_1c: Import complete [id=subnet-0a8f9dbeabb8c6076]
 
Apply complete! Resources: 5 imported, 0 added, 0 changed, 0 destroyed.

これでVPCとサブネットをTerraform管理に取り込むことができました。

取り込んだリソースの設定をterraformで変更してみる

では動作確認も兼ねまして、Terraform管理に取り込んだリソースをTerraformから変更できるか、試してみましょう。

試しに現在の状態でterraform planを実行してみます。

[bash]

$ terraform plan 
aws_vpc.nara: Refreshing state... [id=vpc-0d22e95db506de346]
aws_subnet.nara_private_1a: Refreshing state... [id=subnet-03c0e41a059737435]
aws_subnet.nara_public_1c: Refreshing state... [id=subnet-0a9b87daaf8c81b23]
aws_subnet.nara_private_1c: Refreshing state... [id=subnet-0a8f9dbeabb8c6076]
aws_subnet.nara_public_1a: Refreshing state... [id=subnet-0ce0792423ad2c7c5]
 
No changes. Your infrastructure matches the configuration.
 
Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.

今度はNo changesとなりました。これは「terraformのコードとインフラの状態が一致している」事を意味しており、Terraform管理への取り込みに成功していると言えそうです。

ではTerraformのコードを変更してみます。

今回はタグに「TestTag」を新たに追加してみます(コメントでADDEDと書いている部分が追加したタグです。)

[terraform:vpc-subnet.tf]

resource "aws_subnet" "nara_private_1a" {
 vpc_id            = aws_vpc.nara.id
 availability_zone = "ap-northeast-1a"
 cidr_block        = "172.16.0.64/26"
 tags = {
  Name = "nara-private-1a"
  Test = "TestTag" # ADDED
 }
}
 
resource "aws_subnet" "nara_public_1a" {
 (似た内容のため省略)
}
 
resource "aws_subnet" "nara_public_1c" {
 (似た内容のため省略)
}
 
resource "aws_subnet" "nara_private_1c" {
 (似た内容のため省略)
}
 
resource "aws_vpc" "nara" {
 cidr_block                           = "172.16.0.0/16"
 enable_dns_hostnames                 = false
 enable_dns_support                   = true
 enable_network_address_usage_metrics = false
 instance_tenancy                     = "default"
 tags = {
  Name = "nara"
  Test = "TestTag" # ADDED
 }
}

terraform planを実行してみます。

[bash]

$ terraform plan 
aws_vpc.nara: Refreshing state... [id=vpc-0d22e95db506de346]
aws_subnet.nara_public_1c: Refreshing state... [id=subnet-0a9b87daaf8c81b23]
aws_subnet.nara_private_1a: Refreshing state... [id=subnet-03c0e41a059737435]
aws_subnet.nara_public_1a: Refreshing state... [id=subnet-0ce0792423ad2c7c5]
aws_subnet.nara_private_1c: Refreshing state... [id=subnet-0a8f9dbeabb8c6076]
 
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
 ~ update in-place
 
Terraform will perform the following actions:
 
 # aws_subnet.nara_private_1a will be updated in-place
 ~ resource "aws_subnet" "nara_private_1a" {
    id                                             = "subnet-03c0e41a059737435"
   ~ tags                                           = {
      "Name" = "nara-private-1a"
     + "Test" = "TestTag"
    }
   ~ tags_all                                       = {
     + "Test" = "TestTag"
      # (1 unchanged element hidden)
    }
    # (15 unchanged attributes hidden)
  }
 
 # aws_subnet.nara_private_1c will be updated in-place
 ~ resource "aws_subnet" "nara_private_1c" {
    (似た内容のため省略)
  }
 
 # aws_subnet.nara_public_1a will be updated in-place
 ~ resource "aws_subnet" "nara_public_1a" {
    (似た内容のため省略)
  }
 
 # aws_subnet.nara_public_1c will be updated in-place
 ~ resource "aws_subnet" "nara_public_1c" {
    (似た内容のため省略)
  }
 
 # aws_vpc.nara will be updated in-place
 ~ resource "aws_vpc" "nara" {
    id                                   = "vpc-0d22e95db506de346"
   ~ tags                                 = {
      "Name" = "nara"
     + "Test" = "TestTag"
    }
   ~ tags_all                             = {
     + "Test" = "TestTag"
      # (1 unchanged element hidden)
    }
    # (14 unchanged attributes hidden)
  }
 
Plan: 0 to add, 5 to change, 0 to destroy.
 
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

先頭に~のついている箇所は変更が加わる箇所、+は追加される設定を示しています。追加したタグが差分として出ていることが分かります。

ではapplyしてみましょう。

[bash]

$ terraform apply
 
(terraform plan実行時と同じ内容のため省略)
 
Plan: 0 to add, 5 to change, 0 to destroy.
 
Do you want to perform these actions?
 Terraform will perform the actions described above.
 Only 'yes' will be accepted to approve.
 
 Enter a value: yes
 
aws_vpc.nara: Modifying... [id=vpc-0d22e95db506de346]
aws_vpc.nara: Modifications complete after 1s [id=vpc-0d22e95db506de346]
aws_subnet.nara_public_1a: Modifying... [id=subnet-0ce0792423ad2c7c5]
aws_subnet.nara_private_1a: Modifying... [id=subnet-03c0e41a059737435]
aws_subnet.nara_private_1c: Modifying... [id=subnet-0a8f9dbeabb8c6076]
aws_subnet.nara_public_1c: Modifying... [id=subnet-0a9b87daaf8c81b23]
aws_subnet.nara_public_1a: Modifications complete after 0s [id=subnet-0ce0792423ad2c7c5]
aws_subnet.nara_public_1c: Modifications complete after 0s [id=subnet-0a9b87daaf8c81b23]
aws_subnet.nara_private_1c: Modifications complete after 0s [id=subnet-0a8f9dbeabb8c6076]
aws_subnet.nara_private_1a: Modifications complete after 0s [id=subnet-03c0e41a059737435]
 
Apply complete! Resources: 0 added, 5 changed, 0 destroyed.

applyに成功しました。VPCおよびサブネットがどのようになったか、AWSの画面から確認してみます。

[VPC]

[Subnet]

TestTagが付与されています。Terraformから設定を変更することができました。

まとめ

今回はTerraform v1.5で追加されたimportブロックと、-generate-config-outオプションを紹介しました。リソースをTerraform管理に取り込みむ手順は、v1.5より前とくらべてかなりお手軽になりそうです。

自動生成されたコードは設定の不整合などでそのままでは使えない可能性が高いのですが、全てのパラメータが書かれた状態で出力されます。EC2やRDSなどパラメータ数の多いリソースのコード生成に使うと便利かもしれません。

注意点としては、importブロックを定義しなかったリソースはTerraform管理には取り込まれません。例えばaws_iam_policy_attachmentaws_security_group_ruleなどAWSの画面から連想しづらいリソースの取り込みを忘れないように注意しないといけません。
(今回の検証でも、事前作成したルートテーブルのimportブロックを定義しなかったため、取り込まれませんでした)

ちなみに本稿執筆時点では、Terraform v1.6のアルファビルドが公開されています。このバージョンではterraform testコマンドが実験的機能から正式な機能になるのが目玉となりそうな印象です。
また今回検証したimportブロックも、idを固定文字列ではなく別リソースの参照にすることもできるように機能強化されそうです。
v1.6の新機能についても、機会があれば紹介してみようと思います。

IOWNデジタルツイン事業部では、Terraform EnterpriseおよびTerraform Cloud Plusに関する、日本語による保守サポートをご提供しております。Terraformの導入にあたってお悩みの方は、ぜひお気軽にご相談ください。

HashiCorp製品サポートサービス(弊社ホームページ)

連載シリーズ
著者プロフィール
岩瀬 正太郎