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 파일을 만든다.

 1resource "aws_instance" "demo" {
 2  ami             = "ami-d39a02b5" # ubuntu 16.04
 3  instance_type   = "t2.nano"
 4  subnet_id       = "subnet-xxxxxxxx"
 5  security_groups = ["sg-xxxxxxxx"]
 6  key_name        = "your-key-pair"
 7
 8  connection {
 9    user = "ubuntu"
10    type = "ssh"
11
12    private_key = "${file("~/.ssh/your_private_key.pem")}"
13    timeout     = "2m"
14  }
15
16  provisioner "remote-exec" {
17    inline = [
18      "sudo apt-get update",
19      "sudo apt-get install -y python",
20    ]
21  }
22}

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

 1resource "aws_instance" "demo" {
 2  # 생략
 3  provisioner "remote-exec" {
 4    # 생략
 5  }
 6
 7  provisioner "local-exec" {
 8    command = <<EOF
 9      echo "[demo]" > inventory
10      echo "${aws_instance.demo.public_ip} ansible_ssh_user=ubuntu ansible_ssh_private_key_file=~/.ssh/your_private_key.pem" >> inventory
11      EOF
12  }
13
14  provisioner "local-exec" {
15    command = <<EOF
16      ANSIBLE_HOST_KEY_CHECKING=False \
17      ansible-playbook -i inventory playbook.yml
18      EOF
19  }
20}

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

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

1[demo]
254.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를 설치하도록 간단히 작성했다.

 1---
 2- hosts: demo
 3
 4  tasks:
 5  - name: ping
 6    ping:
 7
 8  - name: install packages
 9    apt: name={{ item }} update_cache=yes state=latest
10    with_items:
11      - git
12    become: true
13
14  - name: install nginx
15    apt: name={{ item }} update_cache=yes state=latest
16    with_items:
17      - nginx
18    become: true

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

 1$ terraform apply
 2
 3aws_instance.demo: Creating...
 4  ami:                          "" => "ami-d39a02b5"
 5  associate_public_ip_address:  "" => "<computed>"
 6  availability_zone:            "" => "<computed>"
 7  ebs_block_device.#:           "" => "<computed>"
 8  ephemeral_block_device.#:     "" => "<computed>"
 9  instance_state:               "" => "<computed>"
10  instance_type:                "" => "t2.nano"
11  ipv6_address_count:           "" => "<computed>"
12  ipv6_addresses.#:             "" => "<computed>"
13  key_name:                     "" => "outsider-aws"
14  network_interface.#:          "" => "<computed>"
15  network_interface_id:         "" => "<computed>"
16  placement_group:              "" => "<computed>"
17  primary_network_interface_id: "" => "<computed>"
18  private_dns:                  "" => "<computed>"
19  private_ip:                   "" => "<computed>"
20  public_dns:                   "" => "<computed>"
21  public_ip:                    "" => "<computed>"
22  root_block_device.#:          "" => "<computed>"
23  security_groups.#:            "" => "1"
24  security_groups.541019735:    "" => "sg-247b2042"
25  source_dest_check:            "" => "true"
26  subnet_id:                    "" => "subnet-2784b551"
27  tenancy:                      "" => "<computed>"
28  volume_tags.%:                "" => "<computed>"
29  vpc_security_group_ids.#:     "" => "<computed>"
30aws_instance.demo: Still creating... (10s elapsed)
31aws_instance.demo: Provisioning with 'remote-exec'...
32aws_instance.demo (remote-exec): Connecting to remote host via SSH...
33aws_instance.demo (remote-exec):   Host: 54.95.180.208
34aws_instance.demo (remote-exec):   User: ubuntu
35aws_instance.demo (remote-exec):   Password: false
36aws_instance.demo (remote-exec):   Private key: true
37aws_instance.demo (remote-exec):   SSH Agent: true
38aws_instance.demo (remote-exec): Connected!
39aws_instance.demo (remote-exec): Setting up libpython2.7-stdlib:amd64 (2.7.12-1ubuntu0~16.04.3) ...
40aws_instance.demo (remote-exec): Setting up python2.7 (2.7.12-1ubuntu0~16.04.3) ...
41aws_instance.demo (remote-exec): Setting up libpython-stdlib:amd64 (2.7.11-1) ...
42aws_instance.demo (remote-exec): Setting up python (2.7.11-1) ...
43aws_instance.demo: Provisioning with 'local-exec'...
44aws_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      "]
45aws_instance.demo: Provisioning with 'local-exec'...
46aws_instance.demo (local-exec): Executing: ["/bin/sh" "-c" "      ANSIBLE_HOST_KEY_CHECKING=False \\\n      ansible-playbook -i inventory playbook.yml\n      "]
47aws_instance.demo: Still creating... (40s elapsed)
48
49aws_instance.demo (local-exec): PLAY [demo] ********************************************************************
50
51aws_instance.demo (local-exec): TASK [Gathering Facts] *********************************************************
52aws_instance.demo (local-exec): ok: [54.95.180.208]
53
54aws_instance.demo (local-exec): TASK [ping] ********************************************************************
55aws_instance.demo (local-exec): ok: [54.95.180.208]
56
57aws_instance.demo (local-exec): TASK [install packages] ********************************************************
58aws_instance.demo (local-exec): ok: [54.95.180.208] => (item=['git'])
59
60aws_instance.demo (local-exec): TASK [install nginx] ***********************************************************
61aws_instance.demo (local-exec): changed: [54.95.180.208] => (item=['nginx'])
62
63aws_instance.demo (local-exec): PLAY RECAP *********************************************************************
64aws_instance.demo (local-exec): 54.95.180.208              : ok=4    changed=1    unreachable=0    failed=0
65
66aws_instance.demo: Creation complete after 55s (ID: i-0116967d4db9e1946)
67
68Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

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

Valid HTML5 Valid CSS WCAG 2.1 AA tested