I am trying to login to ec2 instance that terraform will create with the following code:
resource "aws_instance" "sess1" {
ami = "ami-c58c1dd3"
instance_type = "t2.micro"
key_name = "logon"
connection {
host= self.public_ip
user = "ec2-user"
private_key = file("/logon.pem")
}
provisioner "remote-exec" {
inline = [
"sudo yum install nginx -y",
"sudo service nginx start"
]
}
}
But this gives me an error:
PS C:\Users\Amritvir Singh\Documents\GitHub\AWS-Scribble\Terraform> terraform apply
provider.aws.region
The region where AWS operations will take place. Examples
are us-east-1, us-west-2, etc.
Enter a value: us-east-1
Error: Invalid function argument
on Session1.tf line 13, in resource "aws_instance" "sess1":
13: private_key = file("/logon.pem")
Invalid value for "path" parameter: no file exists at logon.pem; this function
works only with files that are distributed as part of the configuration source
code, so if this file will be created by a resource in this configuration you
must instead obtain this result from an attribute of that resource.
How do I save pass the key from resource to provisioner at runtime without logging into the console?
Have you tried using the full path? Especially beneficial if you are using modules.
I.E:
private_key = file("${path.module}/logon.pem")
Or I think even this will work
private_key = file("./logon.pem")
I believe your existing code is looking for the file at the root of your filesystem.
connection should be in the provisioner block:
resource "aws_instance" "sess1" {
ami = "ami-c58c1dd3"
instance_type = "t2.micro"
key_name = "logon"
provisioner "remote-exec" {
connection {
host= self.public_ip
user = "ec2-user"
private_key = file("/logon.pem")
}
inline = [
"sudo yum install nginx -y",
"sudo service nginx start"
]
}
}
The above assumes that everything else is correct, e.g. the key file exist or security groups allow for ssh connection.
Related
I am setting up several servers in AWS utilizing terraform to deploy them, and ansible to configure (the configuration is quite complex). I would like to accomplish all of this from Terraform but I can't seem to get the ProxyCommand to execute correctly (I believe due to the use of mixed quotes). I need to utilize the ProxyCommand as the commands must be proxied through a bastion host. First I provision the bastion:
resource "aws_instance" "bastion" {
ami = var.ubuntu2004
instance_type = "t3.small"
associate_public_ip_address = true
subnet_id = aws_subnet.some_subnet.id
vpc_security_group_ids = [aws_security_group.temp.id]
key_name = "key"
tags = {
Name = "bastion"
}
}
and then I deploy another server which I would like to configure with Ansible utilizing Terraform's provisioner 'local-exec':
resource "aws_instance" "server1" {
ami = var.ubuntu2004
instance_type = "t3.small"
subnet_id = aws_subnet.some_other_subnet.id
vpc_security_group_ids = [aws_security_group.other_temp.id]
key_name = "key"
tags = {
Name = "server1"
}
provisioner "local-exec" {
command = "sleep 120; ANSIBLE_HOST_KEY_CHECKING=False ansible-playbook -u ubuntu --private-key ~/.ssh/id_rsa --ssh-common-args='-o ProxyCommand='ssh -W %h:%p ubuntu#${aws_instance.bastion.public_ip}'' -i ${self.private_ip} main.yml"
}
}
I have confirmed I can get all of this working if I just have Terraform provision the infrastructure, and then manually run Ansible with the Proxy Command input, but it fails if I try and utilize local-exec, seemingly because I have to incorporate multiple single quotes which breaks the command. Not sure if the bastion variable is done correctly either. Probably a simple fix, but anyone know how to fix this or maybe accomplish this in an easier way? Thanks
Im trying to create a Custom AMI for my AWS Deployment with terraform. Its working quite good also its possible to run a bash script. Problem is it's not possible to create the instance temporary and then to terminate the ec2 instance with terraform and all the depending resources.
First im building an "aws_instance" than I provide a bash script in my /tmp folder and let this be done via ssh connection in the terraform script. Looking like the following:
Fist the aws_instance is created based on a standard AWS Amazon Machine Image (AMI). This is used to later create an image from it.
resource "aws_instance" "custom_ami_image" {
tags = { Name = "custom_ami_image" }
ami = var.ami_id //base custom ami id
subnet_id = var.subnet_id
vpc_security_group_ids = [var.security_group_id]
iam_instance_profile = "ec2-instance-profile"
instance_type = "t2.micro"
ebs_block_device {
//...further configurations
}
Now a bash script is provided. The source is the location of the bash script on the local linux box you are executing terraform from. The destination is on the new AWS instance. In the file I install further stuff like python3, oracle drivers and so on...
provisioner "file" {
source = "../bash_file"
destination = "/tmp/bash_file"
}
Then I'll change the permissions on the bash script and execute it with a ssh-user:
provisioner "remote-exec" {
inline = [
"chmod +x /tmp/bash_file",
"sudo /tmp/bash_file",
]
}
No you can login to the ssh-user with the previous created key.
connection {
type = "ssh"
user = "ssh-user"
password = ""
private_key = file("${var.key_name}.pem")
host = self.private_ip
}
}
With the aws_ami_from_instance the ami can be modelled with the current created EC2 instance. And now is callable for further deployments, its also possible to share it in to further aws accounts.
resource "aws_ami_from_instance" "custom_ami_image {
name = "acustom_ami_image"
source_instance_id = aws_instance.custom_ami_image.id
}
Its working fine, but what bothers me is the resulting ec2 instance! Its running and its not possible to terminate it with terraform? Does anyone have an idea how I can handle this? Sure, the running costs are manageable, but I don't like creating datagarbage....
The best way to create AMI images i think is using Packer, also from Hashicorp like Terraform.
What is Packer?
Provision Infrastructure with Packer Packer is HashiCorp's open-source tool for creating machine images from source
configuration. You can configure Packer images with an operating
system and software for your specific use-case.
Packer creates an temporary instance with temporary keypair, security_group and IAM roles. In the provisioner "shell" are custom inline commands possible. Afterwards you can use this ami with your terraform code.
A sample script could look like this:
packer {
required_plugins {
amazon = {
version = ">= 0.0.2"
source = "github.com/hashicorp/amazon"
}
}
}
source "amazon-ebs" "linux" {
# AMI Settings
ami_name = "ami-oracle-python3"
instance_type = "t2.micro"
source_ami = "ami-xxxxxxxx"
ssh_username = "ec2-user"
associate_public_ip_address = false
ami_virtualization_type = "hvm"
subnet_id = "subnet-xxxxxx"
launch_block_device_mappings {
device_name = "/dev/xvda"
volume_size = 8
volume_type = "gp2"
delete_on_termination = true
encrypted = false
}
# Profile Settings
profile = "xxxxxx"
region = "eu-central-1"
}
build {
sources = [
"source.amazon-ebs.linux"
]
provisioner "shell" {
inline = [
"export no_proxy=localhost"
]
}
}
You can find documentation about packer here.
resource "aws_instance" "appserver1" {
ami = var.imageid
instance_type = var.instancetype
key_name = var.key
security_groups = [aws_security_group.allow_all.name]
connection {
user = "ubuntu"
private_key = file(var.privatekeypath)
}
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install tomcat7 -y"
]
}
}
"terraform validate" gives me the error:
Error: Missing required argument
on main.tf line 52, in resource "aws_instance" "appserver1":
52: connection {
The argument "host" is required, but no definition was found.
You have to specify connection details in the provisioner block. For example:
resource "aws_instance" "appserver1" {
ami = var.imageid
instance_type = var.instancetype
key_name = var.key
security_groups = [aws_security_group.allow_all.name]
provisioner "remote-exec" {
connection {
type = "ssh"
user = "ubuntu"
private_key = file(var.privatekeypath)
host = self.public_ip
}
inline = [
"sudo apt-get update",
"sudo apt-get install tomcat7 -y"
]
}
}
But in your case, using user_data would be more suited.
Instead of using a connection you can make use of userdataEC2 userdata options to install tomcat while launching the deploy instance.
I don't think giving a connection block inside the instance configuration will work
Not able to connect to ec2 instance from terraform. The same key pair works if I create the ec2 instance manually (not via terraform). That confirms my key-pair is correct. Here is the code that I'm trying to do. The error I get: `aws_instance.ec2_test_instance: Provisioning with 'remote-exec'...
Error: Failed to read ssh private key: no key found
Error: Error import KeyPair: MissingParameter: The request must contain the parameter PublicKeyMaterial
status code: 400, request id: `
resource "aws_instance" "ec2_test_instance" {
ami = var.instance_test_ami
instance_type = var.instance_type
subnet_id = var.aws_subnet_id
key_name = aws_key_pair.deployer.key_name
tags = {
Name = var.environment_tag
}
connection {
type = "ssh"
host = self.public_ip
user = "centos"
private_key = "file(path.root/my-key)"
}
provisioner "remote-exec" {
inline = [
"sudo yum -y install wget, unzip",
"sudo yum -y install java-1.8.0-openjdk",
]
}
You will need to use ${} for the interpolation syntax in your path:
private_key = file("${path.module}/my-key")
In the documentation, the example shows ${} around the actual file path within the argument field:
https://www.terraform.io/docs/configuration/functions/file.html
We store our latest approved AMIs in AWS parameter store. When creating new instances with Terraform I would like to programatically get this AMI ID. I have a command to pull the AMI ID but I'm not sure how to use it with Terraform.
Here is the command I use to pull the AMI ID:
$(aws ssm get-parameter --name /path/to/ami --query 'Parameter.Value' --output text)
And here is my Terraform script:
resource "aws_instance" "nginx" {
ami = "ami-c58c1dd3" # pull value from parameter store
instance_type = "t2.micro"
#key_name = "${var.key_name}"
provisioner "remote-exec" {
inline = [
"sudo yum install nginx -y",
"sudo service nginx start"
]
}
}
How can I use the command to pull the AMI ID in the Terraform script?
You can use the aws_ssm_parameter data source to fetch the value of a parameter at runtime:
data "aws_ssm_parameter" "ami" {
name = "/path/to/ami"
}
resource "aws_instance" "nginx" {
ami = data.aws_ssm_parameter.ami.value # pull value from parameter store
instance_type = "t2.micro"
provisioner "remote-exec" {
inline = [
"sudo yum install nginx -y",
"sudo service nginx start"
]
}
}
However, a better approach might be to use the aws_ami data source to filter for the AMI you want more directly instead of pushing the AMI ID to SSM parameter store and then looking it up later. You can filter on a number of criteria including name, account owner and tags. Here's the example from the aws_instance resource documentation that is looking for the latest Ubuntu 20.04 AMI:
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
resource "aws_instance" "web" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
tags = {
Name = "HelloWorld"
}
}
I recommend you use this approach since you already have the AMI ID stored at AWS SSM:
resource "aws_instance" "nginx" {
ami = data.aws_ssm_parameter.ami.value
instance_type = "t2.micro"
provisioner "remote-exec" {
inline = [
"sudo yum install nginx -y",
"sudo service nginx start"
]
}
}
data "aws_ssm_parameter" "ami" {
name = "/production/ami"
}