- 2-2. EC2 생성시 PEM Key 중복 체크 및 생성 코드2024년 06월 22일
- yeongki0944
- 작성자
- 2024.06.22.:54
해당 포스팅은 테라폼 문법을 학습하며, 테스트 용으로 만든 포스팅입니다.
실제 인프라 운영에 적용하기에 부적절합니다.해당 소스의 목적
- EC2 인스턴스를 생성하기 위해 필요한 PEM 키가 존재하는지 확인하고, 존재하지 않는 경우 새로운 키를 생성하여 사용하는 코드
파일 구조
. ├── main.tf ├── provider.tf ├── variables.tf ├── locals.tf ├── key_management.tf └── outputs.tf
주요 개념
- Null Resource
[null_resource 개념]
https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource - null_resource는 Terraform에서 실제 인프라 자원을 생성하지 하지 않음. 주로 프로비저닝할 필요가 없는 작업에 사용.
- null_resource의 기본 동작은 terraform apply 명령어를 처음 실행할 때 한 번만 실행
- trigger를 통해 특정 조건에서 중복 실행 가능.
- 주로 사용되는 케이스
- 스크립트 실행: local-exec나 remote-exec 프로비저너를 사용하여 로컬이나 원격 서버에서 스크립트를 실행 가능.
- 의존성 설정: 특정 리소스가 생성된 후 추가 작업을 수행할 때 사용됨.
- 조건부 논리: 리소스가 변경되었을 때 특정 작업을 수행하도록 설정 가능.
[null_resource 선언 구문]
resource "null_resource" "example" { provisioner "local-exec" { command = "echo This command will execute whenever the configuration changes" } }
[null_resource trigger]
# trigger가 없는 null_resource # terraform apply 명령어를 처음 실행할 때만 딱 한번 실행 resource "null_resource" "example" { provisioner "local-exec" { command = "echo This command will execute only once during apply" } } # trigger가 있는 null_resource resource "null_resource" "example" { # 트리거 > 현재 타임스탬프 기준 # 트리거 값이 변경되면, 실행 > 타임스탬프는 매번 변경되기에 apply시 항상 실행 triggers = { always_run = timestamp() } provisioner "local-exec" { command = "echo This specific command will execute every time during apply as triggers are used" } }
- provisoner
[provisoner 개념]
https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax - Provisioner는 Terraform에서 리소스를 생성한 후 특정 작업을 수행하기 위해 사용함.
- 프로비저너는 리소스 생성 후 스크립트 실행, 파일 복사, 명령 실행 등의 작업을 자동화하는 데 유용함.
[provisoner 주요 유형]
- local-exec
- 로컬 시스템에서 명령을 실행합니다.
- remote-exec
- 원격 시스템에서 명령을 실행합니다.
[provisoner 선언 구문]
resource "null_resource" "example" { provisioner "local-exec" { command = "echo This command will execute" } }
- local-exec
[local-exec 개념]
- local-exec은 Terraform에서 로컬 머신에서 명령을 실행하기 위한 프로비저너
- local-exec 프로비저너를 사용하면, Terraform이 실행되는 머신에서 직접 스크립트나 명령을 실행할 수 있음.
[local-exec 사용 예제]
resource "null_resource" "example" { provisioner "local-exec" { command = "echo This command will execute on the local machine" } }
소스코드
terraform graph ❘ dot -Tpng > graph.png - main.tf
provider "aws" { region = var.region } # EC2 인스턴스를 생성 resource "aws_instance" "ec2_instance" { count = var.instance_count ami = "ami-0c55b159cbfafe1f0" # 원하는 AMI ID로 변경하세요 instance_type = "t2.micro" key_name = var.pem_file_name tags = { Name = "${var.ec2_instance_name}-${count.index}" } depends_on = [null_resource.create_new_key] }
- EC2 인스턴스를 생성하는 코드
- count 매개변수를 사용하여 여러 개의 인스턴스를 생성 가능
- depends_on 을 사용하여 AWS상에 Key가 존재하는지 체크 후 EC2 인스턴스 생성
- key_management.tf
# PEM 폴더를 생성하는 null_resource (폴더가 없는 경우) resource "null_resource" "create_pem_folder" { provisioner "local-exec" { command = "mkdir -p ${path.module}/pem" } triggers = { pem_folder = "${timestamp()}" } } # 키 페어가 존재하지 않으면 새 키를 생성하는 null_resource resource "null_resource" "create_new_key" { provisioner "local-exec" { command = <<EOT if ! aws ec2 describe-key-pairs --key-names ${var.pem_file_name} 2>/dev/null; then aws ec2 create-key-pair --key-name ${var.pem_file_name} --query 'KeyMaterial' --output text > ${local.new_key_file_path} chmod 400 ${local.new_key_file_path} fi EOT } triggers = { create_key = "${var.pem_file_name}-${timestamp()}" } depends_on = [null_resource.create_pem_folder] }
- PEM를 저장할 폴더를 생성
- aws ec2 describe-key-pairs 명령어를 통해, 키 페어가 존재하는지 안하는지 체크
- 키 페어가 존재하지 않으면, aws ec2 create-key-pair 명령어로 키 페어 생성 후, 해당 키 페어를 PEM 폴더에 저장
- triggers에 timestamp를 적용해 항상 실행하여 체크하도록 설정
- variables.tf
variable "pem_file_name" { description = "The name of the PEM key file (without .pem extension)" type = string default = "us-east-2-key-yg" } variable "ec2_instance_name" { description = "The name of the EC2 instance" type = string default = "my-ec2-instance-edit" } variable "region" { description = "The AWS region to create resources in" type = string default = "us-east-2" } variable "instance_count" { description = "The number of EC2 instances to create" type = number default = 3 }
- pem_file_name : 키 페어 이름, 확장자명을 생략하고 입력
- instance_count : count를 사용해서 EC2 리소스 반복 생성
- locals.tf
locals { new_key_file_path = "${path.module}/pem/${var.pem_file_name}.pem" }
- 새로운 키페어가 생성시, 해당 키페어의 상대경로를 output으로 출력하기 위한 locals 변수
- variables를 사용할 경우 에러가 발생
- variables는 외부로부터 입력을 받기 위해 사용
- locals는 내부 계산 및 중간 값을 저장하기 위해 사용
- output.tf
# 새로운 키 파일 경로를 출력하는 output (AWS에서 PEM 키 파일이 없는 경우) output "new_key_file_path" { value = local.new_key_file_path }
코드 실행 결과
- 키 페어가 존재하는 경우
키 페어 존재 pem_file_name을 미리 생성된 pem_key이름과 동일하게 설정 terraform init 실행 전, 후 admin:~/environment/t101/week2/conditional-resource-ec2-pemkey_v2 $ terraform apply -auto-approve \ 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_instance.ec2_instance[0] will be created + resource "aws_instance" "ec2_instance" { + ami = "ami-0c55b159cbfafe1f0" + 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) + disable_api_stop = (known after apply) + disable_api_termination = (known after apply) + ebs_optimized = (known after apply) + get_password_data = false + host_id = (known after apply) + host_resource_group_arn = (known after apply) + iam_instance_profile = (known after apply) + id = (known after apply) + instance_initiated_shutdown_behavior = (known after apply) + instance_lifecycle = (known after apply) + instance_state = (known after apply) + instance_type = "t2.micro" + ipv6_address_count = (known after apply) + ipv6_addresses = (known after apply) + key_name = "us-east-2-key-yg" + monitoring = (known after apply) + outpost_arn = (known after apply) + password_data = (known after apply) + placement_group = (known after apply) + placement_partition_number = (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 + spot_instance_request_id = (known after apply) + subnet_id = (known after apply) + tags = { + "Name" = "my-ec2-instance-edit-0" } + tags_all = { + "Name" = "my-ec2-instance-edit-0" } + tenancy = (known after apply) + user_data = (known after apply) + user_data_base64 = (known after apply) + user_data_replace_on_change = false + vpc_security_group_ids = (known after apply) } # aws_instance.ec2_instance[1] will be created + resource "aws_instance" "ec2_instance" { + ami = "ami-0c55b159cbfafe1f0" + 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) + disable_api_stop = (known after apply) + disable_api_termination = (known after apply) + ebs_optimized = (known after apply) + get_password_data = false + host_id = (known after apply) + host_resource_group_arn = (known after apply) + iam_instance_profile = (known after apply) + id = (known after apply) + instance_initiated_shutdown_behavior = (known after apply) + instance_lifecycle = (known after apply) + instance_state = (known after apply) + instance_type = "t2.micro" + ipv6_address_count = (known after apply) + ipv6_addresses = (known after apply) + key_name = "us-east-2-key-yg" + monitoring = (known after apply) + outpost_arn = (known after apply) + password_data = (known after apply) + placement_group = (known after apply) + placement_partition_number = (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 + spot_instance_request_id = (known after apply) + subnet_id = (known after apply) + tags = { + "Name" = "my-ec2-instance-edit-1" } + tags_all = { + "Name" = "my-ec2-instance-edit-1" } + tenancy = (known after apply) + user_data = (known after apply) + user_data_base64 = (known after apply) + user_data_replace_on_change = false + vpc_security_group_ids = (known after apply) } # aws_instance.ec2_instance[2] will be created + resource "aws_instance" "ec2_instance" { + ami = "ami-0c55b159cbfafe1f0" + 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) + disable_api_stop = (known after apply) + disable_api_termination = (known after apply) + ebs_optimized = (known after apply) + get_password_data = false + host_id = (known after apply) + host_resource_group_arn = (known after apply) + iam_instance_profile = (known after apply) + id = (known after apply) + instance_initiated_shutdown_behavior = (known after apply) + instance_lifecycle = (known after apply) + instance_state = (known after apply) + instance_type = "t2.micro" + ipv6_address_count = (known after apply) + ipv6_addresses = (known after apply) + key_name = "us-east-2-key-yg" + monitoring = (known after apply) + outpost_arn = (known after apply) + password_data = (known after apply) + placement_group = (known after apply) + placement_partition_number = (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 + spot_instance_request_id = (known after apply) + subnet_id = (known after apply) + tags = { + "Name" = "my-ec2-instance-edit-2" } + tags_all = { + "Name" = "my-ec2-instance-edit-2" } + tenancy = (known after apply) + user_data = (known after apply) + user_data_base64 = (known after apply) + user_data_replace_on_change = false + vpc_security_group_ids = (known after apply) } # null_resource.create_new_key will be created + resource "null_resource" "create_new_key" { + id = (known after apply) + triggers = { + "create_key" = (known after apply) } } # null_resource.create_pem_folder will be created + resource "null_resource" "create_pem_folder" { + id = (known after apply) + triggers = { + "pem_folder" = (known after apply) } } Plan: 5 to add, 0 to change, 0 to destroy. Changes to Outputs: + new_key_file_path = "./pem/us-east-2-key-yg.pem" null_resource.create_pem_folder: Creating... null_resource.create_pem_folder: Provisioning with 'local-exec'... null_resource.create_pem_folder (local-exec): Executing: ["/bin/sh" "-c" "mkdir -p ./pem"] null_resource.create_pem_folder: Creation complete after 0s [id=7542075506616754046] null_resource.create_new_key: Creating... null_resource.create_new_key: Provisioning with 'local-exec'... null_resource.create_new_key (local-exec): Executing: ["/bin/sh" "-c" " if ! aws ec2 describe-key-pairs --key-names us-east-2-key-yg 2>/dev/null; then\n aws ec2 create-key-pair --key-name us-east-2-key-yg --query 'KeyMaterial' --output text > ./pem/us-east-2-key-yg.pem\n chmod 400 ./pem/us-east-2-key-yg.pem\n fi\n"] null_resource.create_new_key (local-exec): { null_resource.create_new_key (local-exec): "KeyPairs": [ null_resource.create_new_key (local-exec): { null_resource.create_new_key (local-exec): "KeyPairId": "key-0889a20a1a1099c0e", null_resource.create_new_key (local-exec): "KeyFingerprint": "3a:ee:93:f8:21:b3:9d:b3:ae:1f:87:f7:1c:50:28:4d:46:33:af:0b", null_resource.create_new_key (local-exec): "KeyName": "us-east-2-key-yg", null_resource.create_new_key (local-exec): "KeyType": "rsa", null_resource.create_new_key (local-exec): "Tags": [], null_resource.create_new_key (local-exec): "CreateTime": "2024-06-22T14:43:21.803000+00:00" null_resource.create_new_key (local-exec): } null_resource.create_new_key (local-exec): ] null_resource.create_new_key (local-exec): } null_resource.create_new_key: Creation complete after 1s [id=3573047788361369149] aws_instance.ec2_instance[2]: Creating... aws_instance.ec2_instance[0]: Creating... aws_instance.ec2_instance[1]: Creating... aws_instance.ec2_instance[2]: Still creating... [10s elapsed] aws_instance.ec2_instance[0]: Still creating... [10s elapsed] aws_instance.ec2_instance[1]: Still creating... [10s elapsed] aws_instance.ec2_instance[2]: Still creating... [20s elapsed] aws_instance.ec2_instance[0]: Still creating... [20s elapsed] aws_instance.ec2_instance[1]: Still creating... [20s elapsed] aws_instance.ec2_instance[1]: Creation complete after 21s [id=i-06894b1e9d8c3cf63] aws_instance.ec2_instance[2]: Creation complete after 21s [id=i-09a4eb1e875e86470] aws_instance.ec2_instance[0]: Still creating... [30s elapsed] aws_instance.ec2_instance[0]: Creation complete after 31s [id=i-09797f1ff30513c91] Apply complete! Resources: 5 added, 0 changed, 0 destroyed. Outputs: new_key_file_path = "./pem/us-east-2-key-yg.pem"
첫번째 apply 실행 후 코드 변경없이 두번째 apply > trigger중에 timestamp가 들어간 부분만 다시 실행됨. ec2 생성 완료 ec2 key파일 체크 - EC2 Tag이름 변경
변경 전 tf 코드 수정 tf plan 변경 완료 - 기존 EC2에 새로운 Key pair을 생성해서 적용할 경우
key pair이름 변경 plan 일부 ec2 삭제 pem_key 생성 과정 EC2 생성 및 outputs 다음글이전글이전 글이 없습니다.댓글
스킨 업데이트 안내
현재 이용하고 계신 스킨의 버전보다 더 높은 최신 버전이 감지 되었습니다. 최신버전 스킨 파일을 다운로드 받을 수 있는 페이지로 이동하시겠습니까?
("아니오" 를 선택할 시 30일 동안 최신 버전이 감지되어도 모달 창이 표시되지 않습니다.)