[Osaka/Yokohama/Tokushima] Looking for infrastructure/server side engineers!

[Osaka/Yokohama/Tokushima] Looking for infrastructure/server side engineers!

[Deployed by over 500 companies] AWS construction, operation, maintenance, and monitoring services

[Deployed by over 500 companies] AWS construction, operation, maintenance, and monitoring services

[Successor to CentOS] AlmaLinux OS server construction/migration service

[Successor to CentOS] AlmaLinux OS server construction/migration service

[For WordPress only] Cloud server “Web Speed”

[For WordPress only] Cloud server “Web Speed”

[Cheap] Website security automatic diagnosis “Quick Scanner”

[Cheap] Website security automatic diagnosis “Quick Scanner”

[Reservation system development] EDISONE customization development service

[Reservation system development] EDISONE customization development service

[Registration of 100 URLs is 0 yen] Website monitoring service “Appmill”

[Registration of 100 URLs is 0 yen] Website monitoring service “Appmill”

[Compatible with over 200 countries] Global eSIM “Beyond SIM”

[Compatible with over 200 countries] Global eSIM “Beyond SIM”

[If you are traveling, business trip, or stationed in China] Chinese SIM service “Choco SIM”

[If you are traveling, business trip, or stationed in China] Chinese SIM service “Choco SIM”

[Global exclusive service] Beyond's MSP in North America and China

[Global exclusive service] Beyond's MSP in North America and China

[YouTube] Beyond official channel “Biyomaru Channel”

[YouTube] Beyond official channel “Biyomaru Channel”

Static analysis of Terraform using tflint

My name is Teraoka and I am an infrastructure engineer.
This time, I would like to perform static analysis of Terraform code using tflint.

What is tflint?

This is a linter tool that specializes in tf files published by OSS.
Here the repository .
By using tflint, you can issue warnings if you are writing deprecated syntax or unused declarations, and
detect errors that may occur on cloud platforms such as AWS.

install

For Mac, you can install using brew.

$ brew install tflint

For Linux, an installation script is provided, so you can use that.

$ curl https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash

How to use

Basically run the command in the directory where the tf file is located.
Just execute the following to read the files in the current directory, analyze them, and
display an error if there is a problem.

$tflint

Let's take a closer look.
Write the following code as main.tf in the current directory.

variable "role_arn" { description = "AWS Role Arn" } provider "aws" { region = "ap-northeast-1" assume_role { role_arn = var.role_arn } } resource "aws_instance" "test" { ami = "ami-0ca38c7440de1749a " instance_type = "t3.micro" tags = { Name = "tflint-test" } }

When I check the difference with terraform plan, I am trying to create one EC2 instance.

$ terraform plan ----------------------------------------------- ------------------------- An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_instance.test will be created + resource "aws_instance" "test" { + ami = "ami-0ca38c7440de1749a" + arn = (known after apply) + associate_public_ip_address = (known after apply) + availability_zone = (known after apply) + cpu_core_count = (known after apply) + cpu_threads_per_core = (known after apply) + get_password_data = false + host_id = (known after apply) + id = (known after apply) + instance_state = (known after apply) + instance_type = " t3.micro" + ipv6_address_count = (known after apply) + ipv6_addresses = (known after apply) + key_name = (known after apply) + outpost_arn = (known after apply) + password_data = (known after apply) + placement_group = (known after apply) + primary_network_interface_id = (known after apply) + private_dns = (known after apply) + private_ip = (known after apply) + public_dns = (known after apply) + public_ip = (known after apply) + secondary_private_ips = (known after apply) + security_groups = (known after apply) + source_dest_check = true + subnet_id = (known after apply) + tags = { + "Name" = "tflint-test" } + tenancy = (known after apply) + vpc_security_group_ids = (known after apply ) + ebs_block_device { + delete_on_termination = (known after apply) + device_name = (known after apply) + encrypted = (known after apply) + iops = (known after apply) + kms_key_id = (known after apply) + snapshot_id = (known after apply) + tags = (known after apply) + throughput = (known after apply) + volume_id = (known after apply) + volume_size = (known after apply) + volume_type = (known after apply) } + enclave_options { + enabled = ( known after apply) } + ephemeral_block_device { + device_name = (known after apply) + no_device = (known after apply) + virtual_name = (known after apply) } + metadata_options { + http_endpoint = (known after apply) + http_put_response_hop_limit = (known after apply) + http_tokens = (known after apply) } + network_interface { + delete_on_termination = (known after apply) + device_index = (known after apply) + network_interface_id = (known after apply) } + root_block_device { + delete_on_termination = (known after apply) + device_name = (known after apply) + encrypted = (known after apply) + iops = (known after apply) + kms_key_id = (known after apply) + tags = (known after apply) + throughput = (known after apply) + volume_id = (known after apply) + volume_size = (known after apply) + volume_type = (known after apply) } } Plan: 1 to add, 0 to change, 0 to destroy. ------------ -------------------------------------------------- ---------- Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run.

Let's actually reflect it with terraform apply.

$ terraform apply An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_instance.test will be created + resource "aws_instance" "test" { + ami = "ami-0ca38c7440de1749a" + arn = (known after apply) + associate_public_ip_address = (known after apply) + availability_zone = (known after apply) + cpu_core_count = (known after apply) + cpu_threads_per_core = (known after apply) + get_password_data = false + host_id = (known after apply) + id = (known after apply) + instance_state = (known after apply) + instance_type = "t3.micro" + ipv6_address_count = (known after apply) + ipv6_addresses = (known after apply) + key_name = (known after apply) + outpost_arn = (known after apply) + password_data = (known after apply) + placement_group = (known after apply) + primary_network_interface_id = (known after apply) + private_dns = (known after apply) + private_ip = ( known after apply) + public_dns = (known after apply) + public_ip = (known after apply) + secondary_private_ips = (known after apply) + security_groups = (known after apply) + source_dest_check = true + subnet_id = (known after apply) + tags = { + "Name" = "tflint-test" } + tenancy = (known after apply) + vpc_security_group_ids = (known after apply) + ebs_block_device { + delete_on_termination = (known after apply) + device_name = (known after apply) + encrypted = (known after apply) + iops = (known after apply) + kms_key_id = (known after apply) + snapshot_id = (known after apply) + tags = (known after apply) + throughput = (known after apply) + volume_id = ( known after apply) + volume_size = (known after apply) + volume_type = (known after apply) } + enclave_options { + enabled = (known after apply) } + ephemeral_block_device { + device_name = (known after apply) + no_device = (known after apply) + virtual_name = (known after apply) } + metadata_options { + http_endpoint = (known after apply) + http_put_response_hop_limit = (known after apply) + http_tokens = (known after apply) } + network_interface { + delete_on_termination = (known after apply) + device_index = (known after apply) + network_interface_id = (known after apply) } + root_block_device { + delete_on_termination = (known after apply) + device_name = (known after apply) + encrypted = (known after apply) + iops = (known after apply) + kms_key_id = (known after apply) + tags = (known after apply) + throughput = (known after apply) + volume_id = (known after apply) + volume_size = (known after apply) + volume_type = (known after apply) } } Plan: 1 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: yes aws_instance.test : Creating... aws_instance.test: Still creating... [10s elapsed] aws_instance.test: Still creating... [20s elapsed] aws_instance.test: Still creating... [30s elapsed] aws_instance.test: Still creating ... [40s elapsed] aws_instance.test: Still creating... [50s elapsed] aws_instance.test: Creation complete after 53s [id=i-085049c8fe2c58383] Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

There is no particular problem with the code, so it can be reflected without any problems.
Next, let's rewrite some of the code.

Check the difference with plan again.
You are trying to change t3.micro to t4.micro.

% terraform plan Refreshing Terraform state in-memory prior to plan... The refreshed state will be used to calculate this plan, but will not be persisted to local or remote state storage. aws_instance.test: Refreshing state... [id= i-085049c8fe2c58383] ------------------------------------------------- -------------------------- An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: ~ update in- place Terraform will perform the following actions: # aws_instance.test will be updated in-place ~ resource "aws_instance" "test" { ami = "ami-0ca38c7440de1749a" arn = "arn:aws:ec2:ap-northeast-1:485076298277 :instance/i-085049c8fe2c58383" associate_public_ip_address = true availability_zone = "ap-northeast-1a" cpu_core_count = 1 cpu_threads_per_core = 2 disable_api_termination = false ebs_optimized = false get_password_data = false hibernation = false id = "i-085049c8fe2c58383 " instance_state = "running" ~ instance_type = "t3.micro" -> "t4.micro" ipv6_address_count = 0 ipv6_addresses = [] monitoring = false primary_network_interface_id = "eni-0c78d105cbfaddc16" private_dns = "ip-172-31-40-11.ap-northeast-1. compute.internal" private_ip = "172.31.40.11" public_dns = "ec2-54-249-80-70.ap-northeast-1.compute.amazonaws.com" public_ip = "54.249.80.70" secondary_private_ips = [] security_groups = [ "default", ] source_dest_check = true subnet_id = "subnet-9621c1de" tags = { "Name" = "tflint-test" } tenancy = "default" vpc_security_group_ids = [ "sg-485f1735", ] credit_specification { cpu_credits = "unlimited" } enclave_options { enabled = false } metadata_options { http_endpoint = "enabled" http_put_response_hop_limit = 1 http_tokens = "optional" } root_block_device { delete_on_termination = true device_name = "/dev/xvda" encrypted = false iops = 100 tags = {} throughput = 0 volume_id = "vol-012c9259f69420c1c" volume_size = 8 volume_type = "gp2" } } Plan: 0 to add, 1 to change, 0 to destroy. -------------------- -------------------------------------------------- -- Note: You didn't specify an "-out" parameter to save this plan, so Terraform can't guarantee that exactly these actions will be performed if "terraform apply" is subsequently run.

Let's apply this to reflect it.

% terraform apply aws_instance.test: Refreshing state... [id=i-085049c8fe2c58383] An execution plan has been generated and is shown below. Resource actions are indicated with the following symbols: ~ update in-place Terraform will perform the following actions : # aws_instance.test will be updated in-place ~ resource "aws_instance" "test" { ami = "ami-0ca38c7440de1749a" arn = "arn:aws:ec2:ap-northeast-1:485076298277:instance/i-085049c8fe2c58383" associate_public_ip_address = true availability_zone = "ap-northeast-1a" cpu_core_count = 1 cpu_threads_per_core = 2 disable_api_termination = false ebs_optimized = false get_password_data = false hibernation = false id = "i-085049c8fe2c58383" instance_state = "running" ~ instance_type = "t3.micro" -> "t4.micro" ipv6_address_count = 0 ipv6_addresses = [] monitoring = false primary_network_interface_id = "eni-0c78d105cbfaddc16" private_dns = "ip-172-31-40-11.ap-northeast-1.compute.internal" private_ip = " 172.31.40.11" public_dns = "ec2-54-249-80-70.ap-northeast-1.compute.amazonaws.com" public_ip = "54.249.80.70" secondary_private_ips = [] security_groups = [ "default", ] source_dest_check = true subnet_id = "subnet-9621c1de" tags = { "Name" = "tflint-test" } tenancy = "default" vpc_security_group_ids = [ "sg-485f1735", ] credit_specification { cpu_credits = "unlimited" } enclave_options { enabled = false } metadata_options { http_endpoint = "enabled" http_put_response_hop_limit = 1 http_tokens = "optional" } root_block_device { delete_on_termination = true device_name = "/dev/xvda" encrypted = false iops = 100 tags = {} throughput = 0 volume_id = "vol-012c9259f69420c1c" volume_size = 8 volume_type = "gp2" } } Plan: 0 to add, 1 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_instance.test: Modifying... [id=i-085049c8fe2c58383] aws_instance.test: Still modifying... [id=i-085049c8fe2c58383, 10s elapsed] aws_instance.test: Still modifying... [id= i-085049c8fe2c58383, 20s elapsed] aws_instance.test: Still modifying... [id=i-085049c8fe2c58383, 30s elapsed] aws_instance.test: Still modifying... [id=i-085049c8fe2c58383, 40s elapsed] aws_instance. test: Still modifying... [id=i-085049c8fe2c58383, 50s elapsed] aws_instance.test: Still modifying... [id=i-085049c8fe2c58383, 1m0s elapsed] Error: Client.InvalidParameterValue: Invalid value 't4.micro' for InstanceType. status code: 400, request id: 326502a4-5564-449f-a37d-73f651ffb151 on main.tf line 13, in resource "aws_instance" "test": 13: resource "aws_instance" "test" {

An error has occurred.
Since there is no instance type called t4.micro on AWS, the correct behavior is to get an error, but
when changing the instance type on AWS, you need to stop the instance in question, so do
the same when running on Terraform. The order is Pause -> Change Type -> Start.
In this case, an error occurs when changing the type, and Terraform execution ends while the instance is stopped.
This is not a good idea... If possible, I would like to issue an error at the plan stage.
Now let's run tflint.

% tflint 1 issue(s) found: Error: instance_type is not a valid value (aws_instance_invalid_type) on main.tf line 15: 15: instance_type = "t4.micro"

It gave me an error.
Plan is only an execution plan on Terraform, so
if there is a problem with the syntax of the tf file, it will issue an error, but
it will not be able to detect errors that depend on the specifications of the target cloud platform.
In that case, running tflint first will dramatically improve the accuracy of checking and testing the differences before updating.

summary

As mentioned above, if you apply without being able to detect an error that depends on the specifications of the target cloud platform,
Terraform will not rollback even if an error occurs during execution, so it may affect the running service
or cause an error. You will need to investigate whether this is occurring.
Therefore, it is necessary to improve the accuracy of confirmation before reflection by using plan or tflint, which we introduced this time, as much as possible.
tflint is a very useful tool, so please give it a try.

If you found this article helpful , please give it a like!
2
Loading...
2 votes, average: 1.00 / 12
5,255
X facebook Hatena Bookmark pocket
[2025.6.30 Amazon Linux 2 support ended] Amazon Linux server migration solution

[2025.6.30 Amazon Linux 2 support ended] Amazon Linux server migration solution

The person who wrote this article

About the author

Yuki Teraoka

Joined Beyond in 2016 and is currently in his 6th year as an Infrastructure Engineer
MSP, where he troubleshoots failures while
also designing and building infrastructure using public clouds such as AWS.
Recently, I
have been working with Hashicorp tools such as Terraform and Packer as part of building container infrastructure such as Docker and Kubernetes and automating operations, and I
also play the role of an evangelist who speaks at external study groups and seminars.

・GitHub
https://github.com/nezumisannn

・Presentation history
https://github.com/nezumisannn/my-profile

・Presentation materials (SpeakerDeck)
https://speakerdeck.com/nezumisannn

・Certification:
AWS Certified Solutions Architect - Associate
Google Cloud Professional Cloud Architect