프로비저너로 적용 후 작업 수행하기
프로비저너로 적용 후 작업 수행하기 (Provisioners)
Terraform이 만든 리소스를 서비스하기 위해 파일 업로드, 명령·스크립트 실행 등 post-apply 작업을 수행하는 프로비저너(provisioner) 사용법을 설명해 드릴게요. 단, 가급적 목적에 맞는 전용 솔루션을 먼저 고려해야 해요.
출처: 문서
본문
Terraform으로 만들고 관리하는 리소스를 서비스에 대비시키기 위해 파일을 업로드하고, 명령·스크립트를 실행하고, 다른 작업을 수행해야 할 수 있어요.
경고: Terraform은 주로 불변(immutable) 인프라 운영을 위해 설계되었으므로, post-apply 작업을 수행하기 위한 목적에 맞는 솔루션을 사용할 것을 강력히 권장해요. 목적에 맞는 솔루션을 사용할 수 없을 때, Terraform에 내장된 프로비저너라는 기능을 사용할 수 있어요.
개요 (Overview)
프로비저너를 사용하기 전에, 대상 시스템에서 CLI 명령과 스크립트를 실행하는 것 같은 post-apply 작업을 수행하는 방법에 대한 지침을 프로바이더에서 확인해야 해요. 프로바이더에 리소스 CLI와 상호작용하는 메커니즘이 없다면 프로바이더에 기능 요청 티켓을 여는 것을 권장해요. 자세한 내용은 post-apply 작업 수행을 참고해요.
Terraform에는 머신 이미지나 구성 관리 도구를 사용할 수 없을 때 리소스를 서비스에 대비시키기 위한 몇 가지 내장 프로비저너가 있어요. 구성에서 프로비저너를 사용하기 전에 가능한 모든 대안을 소진해야 해요. Terraform은 구성에 표현된 프로비저너 동작을 예측 가능하게 모델링할 수 없기 때문이에요. 또한 대부분의 프로비저너는 서버에 대한 직접 네트워크 접근과 외부 소프트웨어 설치·구성용 자격 증명을 요구해서, 복잡성과 잠재적 보안 문제를 도입해요.
요구사항 (Requirements)
- 프로비저너를 사용하려면 Terraform v0.9가 필요해요.
provisioner및connection블록에서 일시적(ephemeral) 값을 사용하려면 Terraform v1.10 이상이 필요해요.- 많은 프로비저너가 원격 리소스에 대한 접근을 필요로 해요. 연결 설정 구성은 원격 리소스 연결을 참고해요.
- SSH로 파일을 업로드하는 프로비저너를 사용하려면 원격 시스템에
scp서비스 프로그램이 설치되어 있어야 해요.
리소스에 프로비저너 추가하기 (Add a provisioner to your resource)
작업을 수행하려는 resource 블록에 provisioner "" 블록을 하나 이상 추가해요. 다음 유형을 지정할 수 있어요:
file: Terraform이 실행 중인 머신에서 새 리소스로 파일이나 디렉터리를 복사해요.local-exec: Terraform이 리소스를 만든 후 로컬 머신에서 실행 파일을 호출해요. 자세한 내용은 로컬 머신에서 명령 실행을 참고해요.remote-exec: Terraform이 리소스를 만든 후 원격 리소스에서 실행 파일을 호출해요. 자세한 내용은 원격 리소스에서 명령 실행을 참고해요.
각 프로비저너 유형과 인자에 대한 자세한 내용은 provisioner 블록 레퍼런스를 참고해요. provisioner 블록에서 일시적 값과 민감 값을 사용할 수 있어요.
provisioner 블록의 표현식은 자신의 부모 리소스 이름을 참조할 수 없어요. 대신 self 객체를 사용해요. 부모 resource 참조를 참고해요.
모든 프로비저너는 when과 on_failure 메타-인자를 지원하는데, 이는 프로비저너가 언제 실행되는지와 Terraform 작업이 실패할 때 어떻게 동작할지를 제어하는 데 도움을 줘요.
원격 리소스 연결 (Connect to remote resources)
프로비저너가 SSH 또는 WinRM을 통해 원격 리소스에 연결하도록 구성할 수 있어요. provisioner 블록이나 resource 블록에 connection 블록을 추가해요. 구성의 모든 프로비저너는 resource 블록에 정의된 연결 설정을 사용할 수 있어요. provisioner 블록에 정의된 연결 설정은 해당 프로비저너에만 적용돼요. 자세한 내용은 connection 블록 레퍼런스를 참고해요.
SSH 연결 유형은 새로 생성된 원격 리소스와 가장 자주 사용되므로 SSH 호스트 키 검증은 기본적으로 비활성화돼요. 이것이 수용할 수 없다면 키 배포를 위한 별도 메커니즘을 구축하고 host_key 인자를 명시적으로 설정해 특정 키 또는 서명 CA에 대해 검증할 수 있어요.
connection 블록에서 일시적 값을 사용할 수 있어요. 다음 예시에서 첫 번째 provisioner "file" 블록은 SSH로 루트 사용자로 리소스에 연결해 myapp.conf 파일을 리소스에 복사하고, 두 번째 provisioner "file" 블록은 WinRM으로 관리자 사용자로 리소스에 연결해 리소스의 C:/App 디렉터리에 파일을 복사해요:
provisioner "file" {
source = "conf/myapp.conf"
destination = "/etc/myapp.conf"
connection {
type = "ssh"
user = "root"
password = var.root_password
host = var.host
}
}
provisioner "file" {
source = "conf/myapp.conf"
destination = "C:/App/myapp.conf"
connection {
type = "winrm"
user = "Administrator"
password = var.admin_password
host = var.host
}
}
connection 블록은 다양한 종류의 호스트에 연결하는 것을 지원해요. 추가 인자 구성은 다음 주제를 참고해요:
배스천 호스트로 원격 리소스 연결하기
SSH를 통해 배스천 호스트와 간접 연결을 설정하려면 connection 블록에서 다음 인자를 구성해요:
bastion_host:true로 설정하면 배스천 호스트 연결을 활성화해요.bastion_host_key: 호스트 연결을 검증하기 위한 원격 호스트의 공개 키 또는 서명 CA를 지정해요.bastion_port: 연결할 포트 번호를 지정해요.bastion_user: 연결에 사용할 사용자를 지정해요.bastion_password: 인증에 사용할 비밀번호를 지정해요.bastion_private_key: SSH 키 파일의 내용을 지정해요.file함수로 디스크의 파일에서 키를 로드할 수 있어요.bastion_certificate: 서명된 CA 인증서의 내용을 지정해요.
프록시로 원격 리소스 연결하기
SSH를 통해 HTTP 또는 SOCKS5 프록시로 연결을 설정하려면 connection 블록에서 다음 인자를 구성해요:
proxy_scheme:http,https또는socks5프록시를 지정해요.proxy_host: 프록시 호스트 이름을 지정해요. Terraform은 먼저 프록시 호스트에 연결한 다음host또는bastion_host인자에 지정된 호스트에 연결해요.proxy_port: 프록시 호스트의 포트 번호를 지정해요.proxy_user_name: 연결에 사용할 사용자 이름을 지정해요. HTTP 프록시 서버에 인증이 필요할 때만 이 인자를 포함하면 돼요.proxy_user_password: 연결에 사용할 비밀번호를 지정해요. HTTP 프록시 서버에 인증이 필요할 때만 이 인자를 포함하면 돼요.
부모 resource 참조 (References to the parent resource)
provisioner 블록과 connection 블록의 표현식은 부모 리소스를 이름으로 참조할 수 없어요. 대신 부모 resource 블록을 나타내는 특수한 self 객체를 사용해요. self 객체로 리소스의 모든 속성에 접근할 수 있어요. 예를 들어 self.public_ip를 사용해 aws_instance의 public_ip 속성을 참조해요. 구성에서 참조를 사용하면 의존성이 생기므로, 자기 자신의 블록 안에서 리소스를 이름으로 참조하면 의존성 순환이 생길 수 있기 때문에 리소스를 참조하려면 반드시 self 객체를 사용해야 해요.
로컬 머신에서 명령 실행 (Run commands on the local machine)
Terraform이 설치된 머신에서 명령을 실행하려면 resource 블록에 provisioner "local-exec" 블록을 추가하고 command 인자에 실행할 명령을 지정해요. 다음 예시는 web이라는 aws_instance 리소스를 구성해 IP 주소를 출력하는 로컬 명령을 실행해요:
resource "aws_instance" "web" {
# ...
provisioner "local-exec" {
command = "echo The server's IP address is ${self.private_ip}"
}
}
사용 가능한 인자에 대한 자세한 내용은 provisioner 블록 레퍼런스를 참고해요.
원격 리소스에서 명령 실행 (Run commands on a remote resource)
원격 리소스에서 명령을 실행하려면 remote-exec 프로비저너 유형을 추가해요. 이 프로비저너는 대상 시스템의 CLI를 실행해 해당 시스템의 원격 객체를 생성·업데이트하거나 상호작용할 수 있게 해요. Terraform이 서버에 연결하려면 반드시 connection 블록을 포함해야 해요.
원격 스크립트 실행 (Execute remote scripts)
프로비저너가 SSH로 원격 시스템에서 스크립트를 실행할 때는 보통 스크립트 파일을 원격 시스템에 업로드한 다음 기본 셸로 실행해요. 이 전략 덕분에 환경 변수 값과 스크립트 문장 사이의 다른 컨텍스트를 보존하는 것을 포함해, 해당 셸이 지원하는 모든 일반적인 스크립팅 기법을 사용할 수 있어요.
원격 시스템 경로 (Remote system paths)
Terraform은 프로비저너가 스크립트 파일을 만들 수 있는 원격 파일시스템의 적절한 위치를 요구해요. 기본적으로 Terraform은 target_platform 설정 방식에 따라 다음 패턴으로 임의 숫자를 포함한 경로를 선택해요:
두 경우 모두 프로비저너는 %RAND% 시퀀스를 무작위로 선택된 십진 숫자로 대체해요. 프로비저너는 Terraform이 실행 중인 시스템에서 실행되지 원격 시스템에서 실행되는 것이 아니므로, TMPDIR 같은 원격 환경 변수에 직접 반응하거나 mktemp 같은 함수를 사용할 수 없어요. 그래서 원격 시스템이 이 기본 경로가 기대하는 파일시스템 레이아웃을 사용하지 않을 때 connection 블록의 script_path 인자로 경로를 재정의할 수 있어요:
connection {
# ...
script_path = "H:/terraform-temp/script_%RAND%.sh"
}
프로비저너는 동시에 실행되는 여러 프로비저너 간 충돌 가능성을 줄이기 위해 %RAND% 시퀀스를 무작위로 선택된 십진 숫자로 대체해요. Windows 시스템에서는 일반적인 관례와 달리 역슬래시 대신 슬래시를 사용하는 것을 권장해요. Terraform 언어가 인용 문자열 이스케이프 문자로 역슬래시를 사용하기 때문이에요.
SSH로 SCP를 사용한 스크립트 실행 (Execute scripts using SCP over SSH)
경고: Terraform v1.0 이하를 사용할 때는 신뢰할 수 없는 외부 값을
script_path인자의 일부로 사용하지 마세요.
프로비저너는 %RAND% 확장 후 지정된 스크립트 경로를 원격 scp 프로세스에 직접 전달하며, 이 프로세스가 경로를 해석해요. OpenSSH와 함께 배포되는 기본 scp 구성에서는 상대 경로를 지정해 임시 스크립트를 원격 사용자의 홈 디렉터리에 둘 수 있어요:
connection {
type = "ssh"
# ...
script_path = "terraform_provisioner_%RAND%.sh"
}
민감 값 (Sensitive values)
provisioner 블록에서 민감 값을 사용할 수 있어요. Terraform은 모든 로그 출력에서 민감 값을 숨겨요. 구성에서 민감 값 사용에 대한 자세한 내용은 민감 데이터 관리를 참고해요.
일시적 값 (Ephemeral values)
provisioner 블록과 connection 블록에서 일시적 값을 사용해 프로비저너가 원격 리소스에 연결할 수 있게 할 수 있어요. Terraform은 plan이나 state에 일시적 값을 저장하지 않으며 로그에도 출력하지 않아요. 구성에서 일시적 값 사용에 대한 자세한 내용은 다음 주제를 참고해요:
프로비저너가 작업을 수행할 시점 결정 (Determine when provisioners perform tasks)
기본적으로 Terraform은 리소스 생성이 끝난 직후 프로비저너를 실행하지만, when 인자를 설정해 Terraform 단계를 제어해 프로비저너가 구성된 작업을 언제 수행할지 정할 수 있어요.
생성 시점 프로비저너 (Creation-time provisioners)
리소스 생성 중 실행되는 프로비저너가 실패하면 Terraform은 리소스를 tainted로 표시해서 다음 terraform apply에서 tainted 리소스를 파괴하고 재생성할 수 있게 해요. 생성 시점 프로비저너가 실패하면 리소스가 반쯤 구성된 상태로 남을 수 있기 때문에 Terraform은 리소스를 taint해요. Terraform이 프로비저너 동작을 모델링할 수 없으므로, 리소스의 적절한 생성을 보장하는 유일한 방법은 리소스를 재생성하는 것이에요. on_failure 속성을 continue로 설정하면 기본 taint 동작을 변경할 수 있어요.
파괴 시점 프로비저너 (Destroy-time provisioners)
기본 동작을 변경하려면 when 인자를 destroy로 설정해, Terraform이 부모 리소스를 파괴할 때 프로비저너가 실행되도록 구성해요. 다음 예시는 web을 파괴한 후 echo 명령을 실행하도록 Terraform을 구성해요:
resource "aws_instance" "web" {
# ...
provisioner "local-exec" {
when = destroy
command = "echo 'Destroy-time provisioner'"
}
}
파괴 프로비저너는 리소스가 파괴되기 전에 실행돼요. 프로비저너가 실패하면 Terraform은 오류를 반환하고 다음 terraform apply에서 프로비저너를 다시 실행해요. 파괴 시점 프로비저너가 여러 번 실행되어도 안전하도록 해야 해요. 리소스에서 create_before_destroy 인자를 활성화하면 파괴 시점 프로비저너가 실행되지 못하게 돼요.
파괴 시점 프로비저너를 포함한 resource 블록 제거하기
파괴 시점 프로비저너는 리소스가 파괴되는 시점에 구성에 남아 있을 때만 실행될 수 있어요. 파괴 시점 프로비저너가 있는 resource 블록이 구성에서 완전히 제거되면 그 프로비저너 구성도 함께 제거돼요. 따라서 파괴 프로비저너는 실행되지 않아요. 이를 우회하려면 파괴 시점 프로비저너가 있는 리소스를 안전하게 제거하는 다단계 프로세스를 구성해요:
- 리소스 구성을
count = 0을 포함하도록 업데이트해요. - 구성을 적용해 리소스의 모든 기존 인스턴스를 파괴하고, 여기에는 파괴 프로비저너 실행도 포함돼요.
- 구성에서
provisioner블록과 함께 리소스 블록을 완전히 제거해요. - 구성을 다시 적용해요. 리소스가 이미 파괴되었으므로 추가 조치가 필요 없어요.
이런 제한 때문에 파괴 시점 프로비저너는 드물고 신중하게 사용해야 해요.
Tainted 리소스 (Tainted resources)
taint된 리소스 안의 파괴 시점 프로비저너는 실행되지 않아요. 여기에는 실패한 생성 시점 프로비저너로 taint 표시된 리소스나 terraform taint로 수동 taint된 리소스가 포함돼요.
여러 프로비저너 구성 (Configure multiple provisioners)
resource 블록 안에 여러 프로비저너를 지정할 수 있어요. Terraform은 구성 파일에 정의된 순서대로 프로비저너를 실행해요. 같은 resource 블록에 생성 프로비저너와 파괴 프로비저너를 모두 추가할 수 있지만, Terraform은 주어진 작업에 유효한 프로비저너만 실행해요. 유효한 프로비저너는 구성 파일에 정의된 순서대로 실행돼요.
다음 예시는 순서대로 실행되는 두 개의 local-exec 프로비저너를 포함해요:
resource "aws_instance" "web" {
# ...
provisioner "local-exec" {
command = "echo first"
}
provisioner "local-exec" {
command = "echo second"
}
}
프로비저너 실패 동작 구성 (Configure provisioner failure behavior)
기본적으로 실패한 프로비저너는 terraform apply 명령도 실패하게 해요. Terraform이 작업을 계속하도록 구성하려면 on_failure 인자를 continue로 설정해요. 그러면 Terraform이 오류를 무시하고 작업을 계속해요. 다음에서 echo 명령이 실패해도 Terraform은 web 리소스 생성을 계속해요:
resource "aws_instance" "web" {
# ...
provisioner "local-exec" {
command = "echo The server's IP address is ${self.private_ip}"
on_failure = continue
}
}
타사 프로비저너 설치 (Install third-party provisioners)
Terraform에 내장된 프로비저너만 사용할 것을 권장하지만, 다른 대안이 없을 때는 타사 프로비저너를 플러그인으로 사용할 수 있어요. 타사 프로비저너를 %APPDATA%\terraform.d\plugins 디렉터리, ~/.terraform.d/plugins 디렉터리, 또는 Terraform 바이너리가 설치된 디렉터리에 넣어요.
리소스 없이 프로비저너 실행 (Run provisioners without a resource)
특정 리소스와 직접 연관되지 않은 프로비저너를 실행해야 한다면, terraform_data 리소스와 연결할 수 있어요. terraform_data 리소스의 인스턴스는 표준 리소스 라이프사이클을 구현하지만 실제 인프라 객체를 관리하지는 않아요. 작업을 수행하도록 provisioner 및 connection 블록을 구성할 수 있어요. 또한 input 인자, triggers_replace 인자, 그리고 어떤 메타-인자든 사용해서 의존성 그래프에서 프로비저너가 정확히 어디에서 실행될지 제어할 수 있어요.
다음 예시에서 terraform_data 리소스는 triggers_replace 인자를 포함하며, 이는 cluster의 어떤 인스턴스가 교체될 때 리소스를 다시 프로비저닝하도록 Terraform에 지시해요. 부트스트랩 스크립트가 클러스터의 어떤 인스턴스에서도 실행될 수 있으므로, connection 블록은 먼저 프로비저닝된 첫 번째 인스턴스인 aws_instance.cluster[0]에 연결해요:
resource "aws_instance" "cluster" {
count = 3
# ...
}
resource "terraform_data" "cluster" {
triggers_replace = aws_instance.cluster[*].id
connection {
host = aws_instance.cluster[0].public_ip
}
provisioner "remote-exec" {
inline = [
"bootstrap-cluster.sh ${join(" ", aws_instance.cluster[*].private_ip)}",
]
}
}