DevOps/Terraform

[Terraform] 프로비저너 (Provisioner)

TTOII 2022. 4. 22. 23:21
728x90

✔️ 프로비저너 (Provisioner)

 

Provisioners | Terraform by HashiCorp

Provisioners run scripts on a local or remote machine during resource creation or destruction. Learn how to declare provisioners in a configuration.

www.terraform.io

로컬 머신이나 리모트 머신에 특정 액션(주로 명령어 실행)을 할 수 있게 해준다.

AWS 리소스를 정의할 때도 provisioner를 정의할 수 있다.

테라폼에서 제공하는 공통 argument이다.

 

기본적인 사용법은 다음과 같다.

resource "aws_instance" "web" {
  # ...

  provisioner "local-exec" {
    command = "echo The server's IP address is ${self.private_ip}"
  }
}

 

✔️ 프로비저너 종류

  • file : 파일 복사
  • local_exec : 로컬 머신에서 명령 실행
  • remote_exec : 원격 머신에 명령 실행

✔️ file

file프로비저닝 도구는 파일 또는 디렉토리를 Terraform을 실행중인 시스템에서 새로 생성된 리소스로 복사하는 데 사용된다.
file 프로비저너는 sshwinrm 연결을 모두 지원한다.

✔️ argument

  • source - 소스 파일 또는 폴더이다.
    현재 작업 디렉토리에 대한 상대 경로 또는 절대 경로로 지정할 수 있다.
    이 속성은 content와 함께 지정할 수 없다.

  • content - 대상에 복사할 내용이다.
    대상이 파일이면 내용이 해당 파일에 기록되고 디렉토리의 경우 tf-file-content라는 파일이 생성된다.
    파일을 대상으로 사용하는 것이 좋다. template_file은 여기 또는 모든 interpolation syntax에서 참조될 수 있다.
    이 속성은 source와 함께 지정할 수 없다.

  • destination - (필수) 대상 경로이다. 절대 경로로 지정해야 한다.

예시

resource "aws_instance" "web" {
  # ...

  # Copies the myapp.conf file to /etc/myapp.conf
  provisioner "file" {
    source      = "conf/myapp.conf"
    destination = "/etc/myapp.conf"
  }

  # Copies the string in content into /tmp/file.log
  provisioner "file" {
    content     = "ami used: ${self.ami}"
    destination = "/tmp/file.log"
  }

  # Copies the configs.d folder to /etc/configs.d
  provisioner "file" {
    source      = "conf/configs.d"
    destination = "/etc"
  }

  # Copies all files and folders in apps/app1 to D:/IIS/webapp1
  provisioner "file" {
    source      = "apps/app1/"
    destination = "D:/IIS/webapp1"
  }
}

✔️ local_exec

local-exec 프로비저너는 리소스가 생성된 후 로컬 실행 파일을 호출한다.
이것은 리소스가 아닌 Terraform을 실행하는 시스템에서 프로세스를 호출한다.

✔️ argument

  • command - (필수) 실행할 명령어이다.
    현재 작업 디렉토리에 대한 상대 경로 또는 절대 경로로 제공될 수 있다.
    셸에서 평가되며 환경 변수 또는 Terraform 변수를 사용할 수 있다.

  • working_dir - (선택 사항) 제공된 경우 명령이 실행될 작업 디렉터리를 지정한다.
    현재 작업 디렉토리에 대한 상대 경로 또는 절대 경로로 제공될 수 있다.
    디렉토리가 존재해야 한다.

  • interpreter - (선택 사항) 제공된 경우 명령을 실행하는 데 사용되는 인터프리터 인수 목록다.
    첫 번째 인수는 인터프리터 자체이다. 현재 작업 디렉토리에 대한 상대 경로 또는 절대 경로로 제공될 수 있다.
    나머지 인수는 명령 앞에 추가되며 이를 통해 "/bin/bash", "-c", "echo foo" 형식의 명령줄을 작성할 수 있다.
    인터프리터가 지정되지 않은 경우 시스템 OS에 따라 합리적인 기본값이 선택된다.

  • environment - (선택 사항) 실행된 명령의 환경을 나타내는 키 값 쌍의 블록이다.
    현재 프로세스 환경을 상속한다.
resource "aws_instance" "web" {
  # ...

  provisioner "local-exec" { # 쌍따옴표 안에서 변수를 참조할 때 
    command = "echo ${self.private_ip} >> private_ips.txt"
  }             
}

self는 자기 자신을 의미한다.

✔️ remote_exec

remote-exec 프로비저너는 원격 리소스가 생성된 후 스크립트를 호출한다.
이것은 구성 관리 도구를 실행하고 클러스터로 부트스트랩하는 등의 작업에 사용할 수 있다.

✔️ argument

  • inline - 명령어를 나열할 경우
  • script - 실행할 스크립트가 한개일 경우 경로를 작성
  • scripts - 실행할 스크립트가 여러개일 경우

예시

resource "aws_instance" "web" {
  # ...

  provisioner "file" {
    source      = "script.sh"
    destination = "/tmp/script.sh"
  }

  provisioner "remote-exec" {
    inline = [
      "chmod +x /tmp/script.sh",
      "/tmp/script.sh args",
    ]
  }
}

 

✔️ 프로비저너 연결

 

Provisioner Connection Settings | Terraform by HashiCorp

The connection block allows you to manage provisioner connection defaults for SSH and WinRM.

www.terraform.io

프로비저너가 실행되기 위해서 file, remote-exec 에서는 SSH 연결이 필요하다.

파일을 복사할 때, 원격에서 명령을 실행할 때 SSH 연결이 필요하다.

  • file
  • remote_exec

프로비저너가 연결하기 위한 정보를 주는 argument인 connection 블록이 필요하다.

connection {
    type     = "ssh"
    user     = "root"
    password = "${var.root_password}"
    host     = "${var.host}"
  }

✔️ argument

  • type - ssh/winRM
  • user - ssh 계정
  • password
  • host (필수)
  • port

 

✔️ 프로비저너 연결 방법 1)

provisioner "file" {
  connection {
    type     = "ssh"
    user     = "root"
    password = var.root_password
    host     = self.public_ip
  }
}

하나의 file 프로비저너에 대한 연결이다.

 

✔️ 프로비저너 연결 방법 2) 

provisioner "file" {
}

provisioner "file" {
}

connection { # 모든 프로비저너에게 공통적으로 연결하는 방식 
}

connection 블록을 별도로 선언하면 모든 프로비저너에게 공통적으로 적용된다.

각각의 프로비저너의 연결 방식이 다를 일은 없으므로 후자를 많이 사용한다.

 

✔️ connection 실습

인스턴스를 생성할 때 key 기반 인증 SSH 접속을 위한 connection 블록을 작성해본다.

 

다음은 key를 생성하는 방법이다. 

resource "aws_key_pair" "deployer" {
  key_name   = "deployer-key"
  public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 email@example.com"
}
resource "aws_key_pair" "app_server_key" {
  key_name   = "app_server_key"
  public_key = file("/home/vagrant/.ssh/id_rsa.pub")
}

resource "aws_instance" "app_server" {

  .........리소스 정의........

 connection {
    user        = "ec2-user" 
    host        = self.public_ip
    private_key = file("/home/vagrant/.ssh/id_rsa")
  }  
 
 .........리소스 정의........

}

ssh-keygen을 사용해 키 쌍을 생성하거나 이미 만들어 놓은 키 쌍이 존재한다면

다음과 같이 file(path) 함수를 이용해 public_key로 공개키를 가져온다.  

연결을 위해 connection 블록도 작성한다. 

사용자명은 "ec2-user" 이며 host는 인스턴스 resource 자신의 public_ip이다. 

비밀키도 마찬가지로 file 함수를 이용해 가져온다. 

 

키를 만들었으면 이제 키를 인스턴스에 할당해줘야 한다.

SSH 연결을 위해서는 Key_name을 가져와야 한다. 

key_name               = aws_key_pair.app_server_key.key_name

 

apply 하면 key pair를 새로 생성한다고 출력한다.

# aws_key_pair.app_server_key will be created
  + resource "aws_key_pair" "app_server_key" {
      + arn             = (known after apply)
      + fingerprint     = (known after apply)
      + id              = (known after apply)
      + key_name        = "app_server_key"
      + key_name_prefix = (known after apply)
      + key_pair_id     = (known after apply)
      + public_key      = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC2YV/nHQ7ITrE5ZFVsYmds4YbKdi2lVcXWVg/Vh8r5RaYX8zw28lVBWOtNafLJXP+vRkhHyf9p7/GpgebZum9CwEZ1XFfESvSnLA78DNU5SCF2TNglFoqeub5BXlY3eLokle4PEjOl9L2f2PKK4D8FDTBPM7lT9qcD3dqdupcsvyRtGu2Ba/cXI4FGgKEZYMfU49xUhzOUnH1vxm5kvh3Rgh21qLUK+hL1xeYfTctVtZA9escphXuqd8XU4IMtb6PX6cfGXSqonLwbAM1xjK7HidTI28lfLyO3xDoeUHNfYKkj2csq0Jw7dfjNfBihAhoEVzTot28+e49OsiKuPt6n vagrant@controller"
      + tags_all        = (known after apply)
    }

Plan: 2 to add, 1 to change, 1 to destroy.
╷
│ Error: file provisioner error
│ 
│   with aws_instance.app_server,
│   on main.tf line 28, in resource "aws_instance" "app_server":
│   28:   provisioner "file" {
│ 
│ timeout - last error: dial tcp 52.79.186.246:22: i/o timeout
╵

인스턴스 생성 중간에 에러가 나서 더 이상 작업이 진행되지 않는다.

문제 발생 이유는 terraform을 실행할 때 보안 그룹 설정에서 22번 포트의 접속을 허용하지 않아서

인스턴스는 생성됐으나 프로비저닝 중 ssh 접속이 막혔기 때문이다.

 

ssh는 보안을 위해서 일정 시간동안 사용이 없으면 자동으로 세션을 종료하는 세션 타임아웃(session timeout) 기능이 있다.

amazon linux ami로 인스턴스를 생성하면 기본 timeout 은 5분(300초)이다.

 

정리하자면 ssh 서버 포트인 22번이 막혀있어 인스턴스로 접속이 안되며 timeout은 기본 설정인 5분으로 설정되어 있어 5분 정도의 시간이 지난 후 다음과 같은 에러 메세지가 출력된다.


terraform show로 확인하면 tainted 라고 출력된다.

# aws_instance.app_server: (tainted)

따라서 SSH 접속을 위해서는 반드시 22번 포트를 열어줘야 한다.

 

✔️ Taint

문제가 발생했다는 의미이다.

Tainted 상태가 된 리소스는 다음번 apply 시에 '무조건' 새롭게 만들어진다.

테라폼 입장에서는 자기가 해야되는 작업을 완전하게 못했기 때문에 taint라고 하는 것이며
그래서 다시 apply 했을 때 무조건 새로 만든다.

untaint를 걸어 다음번에 새로 만들지 않도록 설정할 수도 있으며

[vagrant@controller 01]$ terraform untaint aws_instance.app_server
Resource instance aws_instance.app_server has been successfully untainted.

역으로 taint를 걸어 새로 만들도록 설정할 수도 있다.

[vagrant@controller 01]$ terraform taint aws_instance.app_server
Resource instance aws_instance.app_server has been marked as tainted.
728x90