Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Terraform의 provisioner와 Ansible 연동하기

이전 글에서 Terraform의 provisioner를 설명했는데 간단한 프로비저닝은 local-execremote-exec를 사용하면 되지만 보통은 프로비저닝할 때 더 많은 작업을 해야 하므로 이 둘만으로는 충분하지 않고 개인적으로 Terraform에서 전부 다 하는 게 좋다고 보지도 않는다. 프로비저닝을 할 수 있는 도구가 많지만, 최근에 내가 많이 써보려고 하는 Ansible과 연동을 해보고 있다.

일단 EC2 인스턴스를 생성해야 하므로 다음과 같은 간단한 tf 파일을 만든다.

resource "aws_instance" "demo" {
  ami             = "ami-d39a02b5" # ubuntu 16.04
  instance_type   = "t2.nano"
  subnet_id       = "subnet-xxxxxxxx"
  security_groups = ["sg-xxxxxxxx"]
  key_name        = "your-key-pair"

  connection {
    user = "ubuntu"
    type = "ssh"

    private_key = "${file("~/.ssh/your_private_key.pem")}"
    timeout     = "2m"
  }

  provisioner "remote-exec" {
    inline = [
      "sudo apt-get update",
      "sudo apt-get install -y python",
    ]
  }
}

이전 글에서 본 내용과 거의 같으므로 자세한 내용은 이전 글을 참고한다. 여기서는 Ubuntu를 사용했는데 접속한 뒤 remote-exec로 Python만 설치했다. Ansible을 사용할 때 타겟 머신에 Python은 설치되어 있어야 한다. 없으면 MODULE FAILURE 오류가 발생한다.

resource "aws_instance" "demo" {
  # 생략
  provisioner "remote-exec" {
    # 생략
  }

  provisioner "local-exec" {
    command = <<EOF
      echo "[demo]" > inventory
      echo "${aws_instance.demo.public_ip} ansible_ssh_user=ubuntu ansible_ssh_private_key_file=~/.ssh/your_private_key.pem" >> inventory
      EOF
  }

  provisioner "local-exec" {
    command = <<EOF
      ANSIBLE_HOST_KEY_CHECKING=False \
      ansible-playbook -i inventory playbook.yml
      EOF
  }
}

원격 서버 설정은 끝났으므로 이번에는 local-exec를 사용했다. 2번 사용한 것은 그냥 긴 명령어를 보기 좋게 작성하기 위해서다. 로컬에서 Ansible을 실행해서 원격 서버에 접속하는 것이므로 local-exec를 사용한 것이다. 이 말은 로컬 머신에는 Ansible이 설치되어 있어야 한다는 의미이다.

위 명령어를 좀 더 설명하면 Ansible이 서버를 인식할 수 있도록 inventory 파일을 생성한 것이다. 서버를 생성하면서 만들어진 퍼블릭 IP를 이용해서 필요한 파라미터를 지정해서 인벤토리 파일을 만드는데 아래와 같은 형태가 된다. 서버를 만들 때마다 IP가 바뀌므로 자동으로 생성되게 한 것이다.

[demo]
54.95.180.208 ansible_ssh_user=ubuntu ansible_ssh_private_key_file=~/.ssh/your_private_key.pem

두 번째로 나온 local-exec에서 실제로 ansible-playbook을 실행하는데 ANSIBLE_HOST_KEY_CHECKING=False 환경변수는 SSH로 접속할 때 호스트 서버를 확인하는 프롬프트가 나오지 않도록 지정한 것이다. 이게 있으면 terraform apply 중에 프롬프트를 기다리느라 결국 실패하거나 사용자가 입력을 해야 하게 된다. ansible-playbook -i inventory playbook.yml에서 앞에서 만든 인벤토리 파일을 지정하고 실제 프로비저닝의 내용이 있는 playbook.yml을 사용하게 했다. 이 글에서 playbook.yml의 내용은 중요치 않지만, 예제를 위해 다음과 같이 git과 nginx를 설치하도록 간단히 작성했다.

---
- hosts: demo

  tasks:
  - name: ping
    ping:

  - name: install packages
    apt: name={{ item }} update_cache=yes state=latest
    with_items:
      - git
    become: true

  - name: install nginx
    apt: name={{ item }} update_cache=yes state=latest
    with_items:
      - nginx
    become: true

이 테라폼 파일을 실행하면 아래와 같이 Ansible과 자동으로 연동되어 git과 nginx를 설치하는 것을 볼 수 있다.

$ terraform apply

aws_instance.demo: Creating...
  ami:                          "" => "ami-d39a02b5"
  associate_public_ip_address:  "" => "<computed>"
  availability_zone:            "" => "<computed>"
  ebs_block_device.#:           "" => "<computed>"
  ephemeral_block_device.#:     "" => "<computed>"
  instance_state:               "" => "<computed>"
  instance_type:                "" => "t2.nano"
  ipv6_address_count:           "" => "<computed>"
  ipv6_addresses.#:             "" => "<computed>"
  key_name:                     "" => "outsider-aws"
  network_interface.#:          "" => "<computed>"
  network_interface_id:         "" => "<computed>"
  placement_group:              "" => "<computed>"
  primary_network_interface_id: "" => "<computed>"
  private_dns:                  "" => "<computed>"
  private_ip:                   "" => "<computed>"
  public_dns:                   "" => "<computed>"
  public_ip:                    "" => "<computed>"
  root_block_device.#:          "" => "<computed>"
  security_groups.#:            "" => "1"
  security_groups.541019735:    "" => "sg-247b2042"
  source_dest_check:            "" => "true"
  subnet_id:                    "" => "subnet-2784b551"
  tenancy:                      "" => "<computed>"
  volume_tags.%:                "" => "<computed>"
  vpc_security_group_ids.#:     "" => "<computed>"
aws_instance.demo: Still creating... (10s elapsed)
aws_instance.demo: Provisioning with 'remote-exec'...
aws_instance.demo (remote-exec): Connecting to remote host via SSH...
aws_instance.demo (remote-exec):   Host: 54.95.180.208
aws_instance.demo (remote-exec):   User: ubuntu
aws_instance.demo (remote-exec):   Password: false
aws_instance.demo (remote-exec):   Private key: true
aws_instance.demo (remote-exec):   SSH Agent: true
aws_instance.demo (remote-exec): Connected!
aws_instance.demo (remote-exec): Setting up libpython2.7-stdlib:amd64 (2.7.12-1ubuntu0~16.04.3) ...
aws_instance.demo (remote-exec): Setting up python2.7 (2.7.12-1ubuntu0~16.04.3) ...
aws_instance.demo (remote-exec): Setting up libpython-stdlib:amd64 (2.7.11-1) ...
aws_instance.demo (remote-exec): Setting up python (2.7.11-1) ...
aws_instance.demo: Provisioning with 'local-exec'...
aws_instance.demo (local-exec): Executing: ["/bin/sh" "-c" "      echo \"[demo]\" > inventory\n      echo \"54.95.180.208 ansible_ssh_user=ubuntu ansible_ssh_private_key_file=~/.ssh/outsider-aws.pem\" >> inventory\n      "]
aws_instance.demo: Provisioning with 'local-exec'...
aws_instance.demo (local-exec): Executing: ["/bin/sh" "-c" "      ANSIBLE_HOST_KEY_CHECKING=False \\\n      ansible-playbook -i inventory playbook.yml\n      "]
aws_instance.demo: Still creating... (40s elapsed)

aws_instance.demo (local-exec): PLAY [demo] ********************************************************************

aws_instance.demo (local-exec): TASK [Gathering Facts] *********************************************************
aws_instance.demo (local-exec): ok: [54.95.180.208]

aws_instance.demo (local-exec): TASK [ping] ********************************************************************
aws_instance.demo (local-exec): ok: [54.95.180.208]

aws_instance.demo (local-exec): TASK [install packages] ********************************************************
aws_instance.demo (local-exec): ok: [54.95.180.208] => (item=['git'])

aws_instance.demo (local-exec): TASK [install nginx] ***********************************************************
aws_instance.demo (local-exec): changed: [54.95.180.208] => (item=['nginx'])

aws_instance.demo (local-exec): PLAY RECAP *********************************************************************
aws_instance.demo (local-exec): 54.95.180.208              : ok=4    changed=1    unreachable=0    failed=0

aws_instance.demo: Creation complete after 55s (ID: i-0116967d4db9e1946)

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

이제 이후부터는 ansible을 어떻게 사용할지만 고민하면 된다.

2018/01/26 04:30 2018/01/26 04:30