티스토리 뷰


이전 포스팅에서 이어지는 글이다.


1) Packer로 이미지 생성하기


설치 페이지 


튜토리얼 페이지


Packer는 Hashicorp라는 회사에서 만든 오픈소스 이미지 빌드 자동화 도구이다.

기본적인 사용방법은 위의 설치 페이지에서 Packer를 설치했으면, 실행 파일 (packer.exe) 이 있는 위치에서 cmd 명령으로


packer build filename.json


을 실행하면 이미지를 만들어준다.


파일이름의 확장자에서 볼 수 있듯 Packer에서는 JSON 형식으로 VM 이미지에 대한 설정을 정의하는데 이 JSON 파일을 템플릿 파일이라 부른다. 템플릿 파일에는 뭘 써야하는지 튜토리얼 페이지의 예제 코드를 먼저 보고 얘기해보자.


{

  "variables": {

    "aws_access_key": "XXXXXXXXXXXX",

    "aws_secret_key": "XXXXXXXXXXXXXXXXXXXX"

  },

  "builders": [{

    "type": "amazon-ebs",

    "access_key": "{{user `aws_access_key`}}",

    "secret_key": "{{user `aws_secret_key`}}",

    "region": "us-east-1",

    "source_ami_filter": {

      "filters": {

        "virtualization-type": "hvm",

        "name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*",

        "root-device-type": "ebs"

      },

      "owners": ["099720109477"],

      "most_recent": true

    },

    "instance_type": "t2.micro",

    "ssh_username": "ubuntu",

    "ami_name": "packer-example {{timestamp}}"

  }]

}


Packer 템플릿 파일을 구성하는 기본적인 요소로 Variable, Builder, Provisioner, Post-Processor가 있는데 이 중에 Builder만 필수고 나머지는 선택이다. 각각의 역할은 아래와 같다.


- Variable: 변수를 정의한다. 클라우드 프로젝트 이름이나 VM 이름같이 그때그때 달라질 수 있는 것들을 넣거나, 인증 키 같은걸 넣거나 한다. 위 예시에서처럼 그냥 템플릿 파일에 때려박을 수도 있지만 cmd에서 변수값을 전달받거나 다른 파일로부터 전달받거나 하는 것도 가능하다.


- Builder: 이미지 빌드 툴로서의 핵심적인 기능을 담당하는 부분이다. 어떤 클라우드에 올릴 것인지, 리전 (데이터센터의 물리적 지역) 은 어디로 할 것인지, 어떤 OS를 쓸 것인지, 임시로 만들 VM의 타입은 어떤 걸로 할 것인지 등을 여기서 정의한다.


- Provisioner: OS와 관련된 기본적인 설정을 마친 뒤 Ansible 같은 Configuration Management 툴과 연계하여 소프트웨어를 설치할 수 있도록 해주는 부분이다. 위 예시에는 없는데 뒤에서 Ansible을 사용해서 '소프트웨어가 설치된 상태의 이미지' 를 만드는 방법을 알아볼 것이다.


- Post-Processor: Builder, Provisioner를 거쳐 이미지 생성이 끝난 뒤 추가적으로 실행될 명령어를 지정할 수 있다.


예제 코드의 대부분을 차지하는 builder에 들어가는 각 attribute들에 대해 간단하게 설명하면 이렇다.


- type: 어떤 클라우드에 이미지를 만들 것인지 정한다. 다른 예로 구글 클라우드면 "googlecompute" 라고 쓴다.

- access_key & secret_key: 내 클라우드 계정에 접근하기 위한 인증 키 쌍이다. 이걸 만들어야 클라우드 관련 작업을 외부에서 할 수 있게 되는데, 만드는 방법은 AWS 콘솔에서 오른쪽 위에 있는 사용자 이름을 클릭하면 "내 보안 자격 증명" 이라는 메뉴가 나온다. 그걸 클릭하면 아래 사진과 같이 "액세스 키" 라는 섹션이 보일 것이다. 



아래쪽에 있는 "새 액세스 키 만들기" 버튼을 누르면 액세스 키 ID (access_key) 와 비밀 액세스 키 (secret_key) 를 쌍으로 준다. 액세스 키 ID는 만들고 나서도 저 화면에서 계속 확인할 수 있지만 비밀 키는 처음 만들 때 한번만 보여주고 그 뒤로는 안 보여주니까 따로 어디 저장해놓던지 해야 한다. 

이 키쌍이 유출되면 다른 사람이 내 클라우드에 접근해서 자원을 왕창 빌려쓰고 요금 폭탄을 맞는 사태가 발생할 수 있으므로 안전하게 관리해야 한다. (실수로 유출된다면 저 화면에 들어가서 키쌍을 삭제시켜서 무효로 만들어야 한다)


- region: 클라우드 업체들은 세계 각지에 물리적 데이터센터를 가지고 있는데 그 중 어떤 지역에 VM을 배치할 것인지를 정한다. 예를 들면 us-east-1은 미국 동부 - 버지니아 북부고 ap-northeast-2는 아시아 태평양 - 한국 서울 지역이다. 그 외에도 어떤 리전들이 있는지는 이 페이지에서 확인하자.

- source_ami_filter: 다른건 사실 잘 모르겠고 'name' 이 바로 이미지를 선택하는, 즉 OS를 결정하는 부분이다. 이 때 AWS에 올라와 있는 '퍼블릭 이미지' 중에서 선택을 해야 하는데 퍼블릭 이미지 리스트는 AWS 콘솔에서 AMI 탭 - 퍼블릭 이미지 필터를 선택하면 볼 수 있다.

- instance_type: 이미지 생성을 위해 임시로 만드는 VM의 타입을 넣는다. 타입이란 쉽게 말하면 VM의 성능, 그러니까 CPU 코어 개수나 메모리 사이즈를 정의해놓은 것을 뜻한다. AWS EC2에 어떤 타입들이 있는지는 이 페이지에서 확인하자.

- ssh_username: Packer가 임시 VM에서 작업할 때 쓰는 username이다. 우분투니까 기본 이름 ubuntu로 하자.

- ami_name: 말 그대로 이미지에 고유한 이름을 붙일 수 있다. {{timestamp}} 라고 써놓은건 만든 시간에 따라 바뀌는 값이니 이 스크립트를 여러번 실행하더라도 이름이 중복되는 이미지가 여러개 생길 일은 없다.


그럼 한번 실행해보자.

위의 예제 코드에서 access_key, secret_key만 내 키로 바꿔주고 나머지는 그대로 붙여넣으면 된다.

그 다음 처음에 얘기했던대로 packer build를 실행한다. 이 파일의 이름은 aws_example.json으로 했다.


packer build aws_example.json


를 실행하면 cmd 창에 뭔가 막 뜰 것이다. "builds finished" 어쩌구 메시지가 뜰 때까지 기다리자.

성공적으로 실행됐으면 AWS 콘솔 창에서 EC2 -> AMI 탭에 들어가보자. 아래 사진처럼 항목이 하나 생겨있을 것이다.



2) libcloud를 이용한 AWS API 이용


이미지를 만들었으니 이 이미지로 VM을 만들고, 그 VM에 접속해서 실제로 우분투 OS가 잘 깔렸는지 확인해보자.


그냥 AWS 콘솔에서 작업을 수행해도 되지만, 원클릭 자동화가 목표니까 이것도 프로그램으로 하고 싶다.

AWS 개발자 도구를 따로 받아서 써도 되지만 파이썬을 쓴다면 libcloud라는 편리한 라이브러리가 있다. 여러 클라우드 서비스들의 API를 모아놓은 라이브러리로 AWS가 아니라 다른 클라우드 (구글 클라우드나 Azure, OpenStack까지도) 를 쓰더라도 이용할 수 있다.


파이썬 라이브러리니까 pip를 이용해서 설치한다.


pip install apache-libcloud


설치했으면 예제 코드를 보면서 어떻게 쓰는지 알아보자.


from libcloud.compute.types import Provider

from libcloud.compute.providers import get_driver


ACCESS_ID = 'XXXXXXXXXXXXXXXXXX'

SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXX'


IMAGE_ID = 'ami-XXXXXXXXXXXX'    # 위에서 Packer로 만든 이미지의 ID

SIZE_ID = 't1.micro'

KEYPAIR_NAME = 'XXXXXXXX'    # 키 페어를 미리 만든 뒤 여기다가 키 이름을 입력 (밑에서 설명)


cls = get_driver(Provider.EC2)    # AWS EC2에 대한 API를 사용하겠다고 지정

driver = cls(ACCESS_ID, SECRET_KEY, region="us-east-1")    # 인증, 리전 선택


# 머신 타입의 종류와 AMI에 현재 등록되어있는 이미지들을 불러와서 내가 입력한 거랑 매칭되는걸 찾는다

sizes = driver.list_sizes()

images = driver.list_images()

size = [s for s in sizes if s.id == SIZE_ID][0]

image = [i for i in images if i.id == IMAGE_ID][0]


node = driver.create_node(name='testnode', image=image, size=size, ex_keyname=KEYPAIR_NAME)    # VM 생성


libcloud에서는 driver라는 개념이 중요하게 쓰이는데 간단하게 말하면 각 클라우드별 API를 담고 있는 클래스라고 할 수 있다.


대부분은 코드에 달아놓은 주석만 봐도 간단하게 이해할 수 있겠지만 설명이 필요한 부분도 좀 있다. 먼저 키 페어.

위에서 봤던 API 쓸 때 필요한 access key & secret key하고는 별개의 존재다. 앞에서 봤던 그건 "내 AWS 계정" 에 대한 인증 키고, 지금 얘기하는 키 페어라는 건 "지금 만들 VM" 에 접속하기 위한 인증 키다.


VM에 접속할 때 SSH를 이용하게 되는데, 접속할 때 공개키 방식으로 사용자 인증을 한다. 리눅스에다가 SSH 서버를 깔면 .ssh라는 폴더가 생기는데, .ssh/authorized_keys라는 파일 안에 들어있는 공개키랑 내가 접속할 때 쓰는 비밀키가 매칭이 돼야 인증이 되는 방식이다.


코드의 마지막 줄에서 VM을 생성할 때 키페어 이름을 지정해주는 파라미터가 있는 이유가 바로 authorized_keys 파일 안에 공개키를 넣어주기 위함이며, 접속하기 위해서는 그 키랑 매칭이 되는 비밀키 파일을 가지고 있어야 한다. 이 과정을 거치지 않으면 VM을 만들 수는 있는데 접속을 할 수가 없다.


그래서 키 페어를 만들어야 한다. 키 페어가 만들어지면 .pem이라는 확장자로 끝나는 비밀키 파일을 다운로드받게 되는데 이걸 Putty같은 SSH 클라이언트로 접속할 때 써주면 된다.

(단 Putty의 경우 .pem 확장자가 아니라 .ppk 확장자를 쓰는데 변환을 위해서 또 Puttygen이라는걸 쓰게 된다... SSH 접속에 관한 내용은 여기다 다 쓰기엔 너무 길어지기도 하고 본론이랑 약간 동떨어진 내용이니 이 문서를 참고하자)


근데 키 페어 설정까지 제대로 했는데도 접속이 안된다. Connection timed out 어쩌구 하는 에러가 뜨면서 접속이 안될 것이다.

이유는 VM을 처음 만들면 기본적으로 적용되어 있는 방화벽 규칙 때문이다. SSH 접속을 위해서는 22번 포트를 열어줘야 한다. 외부에서 VM으로 접속하는 거니까 인바운드 규칙으로 22번 포트에 대해서 모든 접근 허용으로 만들어준다. 콘솔에서는 EC2 -> '보안 그룹' 탭에서 편집할 수 있고, API로는 위 코드와 같이 한다.


키 페어랑 방화벽 설정까지 해주고 나서야 비로소 VM에 접속할 수 있게 됐다. 우분투 16.04 버전이 설치됐다는 걸 확인할 수 있다.



3) Ansible로 소프트웨어 설치 및 설정하기


위 과정을 거쳐서 우분투 VM을 만드는 데는 성공했고, 이제 그 위에 소프트웨어 스택 (Apache 웹서버, PHP, MySQL/MariaDB 데이터베이스) 을 올릴 차례다.


이런 예시에서 Ansible을 사용하는 방법이 크게 두 가지가 있는데,


- Packer의 Provisioner 이용하기. 앞에서 Packer 스크립트에 대해서 설명할 때 언급했듯 이미지를 만들 때 Ansible과 연계하여 소프트웨어 설치까지 한번에 끝낼 수 있다. 원리는 Packer가 이미지를 뽑아내기 전에 임시 VM을 만드는데 거기다 Ansible을 같이 설치하고, Ansible 스크립트를 전송해서 거기서 실행하는 것이다. Packer를 실행하는 내 컴퓨터에 Ansible을 설치하지 않아도 되고, OS 설치와 함께 소프트웨어까지 설치하는 간편한 방법이다.


- 별도의 머신에 Ansible 설치해서 원격 접속하기. Packer 스크립트의 결과물로 나온 이미지를 이용해서 VM을 만든 다음, 별도의 머신에서 해당 VM에 접속하여 소프트웨어를 설치하는 것이다. 이 방법은 전자보다 꽤 까다로운데 일단 Ansible은 윈도우에서 설치가 안된다. 그래서 일반적인 윈도우 사용자라면 VirtualBox나 VMWare 등으로 리눅스 가상머신을 만들어서 그 안에서 작업을 진행해야 한다. 게다가 원격 접속을 할 때는 SSH를 사용하는데 이때 키 교환 및 인증 절차가 들어가서 좀 복잡하다. 하지만 이미 만들어진 VM에 원격으로 소프트웨어를 설치해야 하는 경우에는 이 방법을 이용해야 하므로 알아둘 필요가 있다.


이 글에서는 이미지를 만들 때 한번에 소프트웨어를 설치하면 되니까 전자의 방법으로 하기로 하자. 후자는 다음에 Ansible에 대해서 더 얘기할 기회가 있을테니 그때 다루기로 한다.


그럼 Ansible을 어떻게 쓰는지 알아보자.

Ansible에는 playbook이라는 개념이 있는데, playbook이라는 것은 대상 머신에 어떤 명령을 내릴지를 모아놓은 스크립트 파일을 의미한다. playbook은 YAML이라는 마크업 언어를 사용해서 작성한다. JSON이나 XML이랑 비슷한 형식이라고 생각하면 되지만 상대적으로 최근에 뜨기 시작한 언어라서 생소할 수 있다.


우리가 쓸 Ansible playbook이 어떻게 생겼는지 보자.


- hosts: all

  tasks:

        - name: install apache2

          become: true

          shell: apt-get -y install apache2

        - name: install php7

          become: true

          shell: apt-get -y install php7.0 libapache2-mod-php7.0


YAML에서는 - 랑 스페이스바로 명령을 구분한다. 탭이 아니라 스페이스바를 써야 한다는 점에 유의!


맨 위에 hosts: all 이라고 되어있는 건, 원래 Ansible에서는 "hosts" 라는 파일이 있어서 거기다가 대상 머신의 IP 주소 리스트를 쭉 적게 되어있다. all이라고 쓰면 그 파일에 써있는 모든 대상에게 이 playbook의 내용을 적용하겠다는 뜻이다. 단 이 예제에서는 나중에 Packer 스크립트에서 localhost (자기 자신) 에게만 적용되도록 만들어 줄 것이므로 별로 의미없다.


tasks: 아래로 - 가 나올때마다 하나의 명령을 정의하는데, name은 말 그대로 해당 명령의 이름을 정해주면 된다. 

become: true라는 것은 루트 권한으로 실행한다는 뜻으로 쉽게 말해서 sudo랑 같은 의미다.

shell: 이라고 되어있는 부분이 실제 명령이라고 할 수 있는데, Ansible에는 대상 머신을 가지고 어떤 일을 하기 위한 명령들이 많이 정의되어 있고 이것들을 "모듈" 이라 부른다. shell은 모듈의 일종으로, 이름대로 리눅스 shell에다가 뒤에 나오는 문장을 명령어로 쓴다는 뜻이다. 즉 첫번째 task는 우분투 터미널에 들어가서 루트 권한으로 "apt-get -y install apache2" 를 치는 것과 같은 일을 한다.


참고로 shell 모듈로 apt-get... 을 실행하는 대신 "apt" 라는 모듈을 쓸 수도 있고 Ansible에서는 그걸 더 권장한다. 근데 분명 같은 일을 해야하는데 미묘하게 디테일이 다른지 shell로 apt-get 하면 설치가 되는게 apt 모듈을 쓰면 에러가 나기도 하더라... 그래서 일단 예시는 shell 모듈로 다 했다.


여튼 저 playbook을 실행하면 그러니까 apache2랑 php7.0, libapache2-mod-php7.0이라는 것들을 설치할 것이다. 마지막 건 php랑 아파치 웹 서버를 연동하기 위한 패키지 같다.

DB를 설치하는 부분은 없는데 일단 웹 서버랑 PHP부터 테스트해보고 DB는 이따가 따로 하도록 하자.


이제 Packer 스크립트에서 저 playbook을 실행하도록 만들어줄 것이다.


{

  "variables": {

    "aws_access_key": "XXXXXXXXXXXXXX",

    "aws_secret_key": "XXXXXXXXXXXXXXXXXXXXXX"

  },

  "builders": [{

    "type": "amazon-ebs",

    "access_key": "{{user `aws_access_key`}}",

    "secret_key": "{{user `aws_secret_key`}}",

    "region": "us-east-1",

    "source_ami_filter": {

      "filters": {

        "virtualization-type": "hvm",

        "name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*",

        "root-device-type": "ebs"

      },

      "owners": ["099720109477"],

      "most_recent": true

    },

    "instance_type": "t2.micro",

    "ssh_username": "ubuntu",

    "ami_name": "packer-example {{timestamp}}"

  }],

    "provisioners":[

    {

      "type":"shell",

      "execute_command":"echo 'install ansible' | {{ .Vars }} sudo -E -S sh '{{ .Path }}'",

      "inline":[

                "sleep 30",

                "apt-add-repository ppa:rquillo/ansible",

                "/usr/bin/apt-get update", 

                "/usr/bin/apt-get -y install ansible"

                ]

    },

    {

       "type":"ansible-local",

       "playbook_file":"./aws_playbook.yml"

    },

{

"type": "shell",

"inline":["sudo chown ubuntu: /var/www"]

},

{

"type": "file",

"source": "index.php",

"destination": "/var/www/index.php"

}

  ]

 

}


builders까지는 앞에서 봤던 거랑 똑같다. 추가된건 provisioner 부분인데, 설명했듯 여기서 OS가 설치된 이미지 위에다가 소프트웨어를 깔거나 파일을 복사하는 등의 명령을 내릴 수 있고 Ansible과의 연동이 가능하다.


provisioner 안에 중괄호로 나누어진 4개의 파트가 있는데 각각 하는 일을 설명하면 이렇다.


1: shell 명령을 통해 Ansible을 설치하는 부분이다. Ansible의 모듈이랑 마찬가지로 Packer Provisioner도 지정된 몇 가지의 provisioner type이 있어서 그 중에 골라쓸 수 있는데 shell이라는 타입을 쓰면 말 그대로 shell 명령어를 실행할 수 있다.

2: ansible-local 타입을 쓰면 ansible이 자기 자신 (local) 을 대상으로 지정된 playbook을 실행하도록 한다.


4: 이건 Ansible하곤 상관없고 Packer의 provisioner type 중 file provisioner라는 건데, 내 컴퓨터에 준비해놓은 index.php라는 파일을 대상 이미지의 지정 경로에 복사한다. 저 경로에 복사하겠다는 것은 웹 서버 주소/index.php로 접속하면 저 파일의 내용이 뜨게 만들겠다는 소리다 (playbook을 통해 아파치 웹 서버를 설치했을 것이기에). 

3: file provisioner를 실행하기 전에 이걸 안해주면 저 디렉토리에 대한 파일쓰기 권한이 없어서 permission denied 에러가 뜬다. 그래서 sudo chown 명령어를 통해 디렉토리에 대한 권한을 획득하는 부분이다.


index.php 파일은 간단하게 PHP의 동작 여부만 테스트할 수 있도록 만들었다.


<?php

echo "Hello World!";


이제 다시 packer 스크립트를 실행할 차례다. aws_playbook.yml이랑 index.php 파일이 packer 실행 파일과 같은 폴더에 있어야 한다.


packer build aws_example.json


아까하곤 다르게 cmd 창에 뭔가 복잡한 텍스트가 더 많이 지나가는데 Ansible 때문에 그렇다. Ansible이 실행되다가 중간에 FAILED! 라고 뜨고 이미지가 생성되지 않는다면 playbook 코드에 문제가 없나 다시 검토해보자.


이미지가 제대로 생성됐다면 2) 의 프로그램을 한번 더 실행해주고 (물론 새 이미지의 ID를 넣어줘야 한다), VM이 생성되면 AWS 콘솔에서 VM의 퍼블릭 IP를 확인해보자.


http://xxx.xxx.xxx.xxx/index.php 로 접속했을 때 Hello World! 메시지가 뜬다면 Apache 웹 서버랑 PHP가 제대로 설치됐다는 것을 확인한 것이다.

(index.php 말고 그냥 IP 주소만 치거나 index.html로 들어가면 Apache 웹 서버 디폴트 페이지가 뜰 것이다)



이제 데이터베이스도 세팅해보자. 먼저 DB 테스트를 하기 위한 파일 몇 개를 Packer file provisioner로 복사해줄 것이다.


   {

"type": "file",

"source": "db.php",

"destination": "/var/www/html/db.php"

},

{

"type": "file",

"source": "dump.sql",

"destination": "/var/www/html/dump.sql"

},

{

       "type":"ansible-local",

       "playbook_file":"./aws_playbook_2.yml"

    }


provisioner 대괄호 안에 이 코드를 넣어야 한다. 

db.php는 DB 동작 여부를 테스트하기 위한 페이지고, dump.sql은 DB에 테스트용 데이터를 넣기 위한 SQL 파일이다. 

각각 이렇게 만들어보자.


<?php


$connection = new PDO('mysql:host=localhost;dbname=demo', 'demo', 'demo');

$statement  = $connection->query('SELECT message FROM demo');


echo $statement->fetchColumn();

CREATE TABLE IF NOT EXISTS demo (

  message varchar(255) NOT NULL

) ENGINE=MyISAM DEFAULT CHARSET=utf8;


INSERT INTO demo (message) VALUES('Hello World!!');


마지막의 aws_playbook_2.yml은 데이터베이스 설치 및 세팅을 위한 Ansible playbook으로 아래와 같다.


- hosts: all

  tasks:

        - name: start mysql service

          become: true

          service: name=mysql state=started enabled=true

        - name: install python mysql package

          become: true

          shell: apt-get -y install python-mysqldb

        - name: create a new database

          become: true

          mysql_db: name=demo state=present collation=utf8_general_ci

        - name: create a database user

          become: true

          mysql_user: name=demo password=demo priv=*.*:ALL host=localhost state=present

        - name: insert sample data

          become: true

          shell: cat /var/www/html/dump.sql | mysql -u demo -pdemo demo

        - name: install mysql extension for php

          become: true

          shell: apt-get -y install php7.0-mysql


service, mysql_db, mysql_user라는 모듈들이 추가로 쓰였다. service 모듈은 특정한 서비스를 켜거나 끌 수 있다 (state=started라는게 현재 꺼져있으면 켜라는 소리다). mysql_db, mysql_user라는 모듈들은 이름대로 mysql에 관련된 작업들을 쉽게 할 수 있도록 도와주는 모듈들인데 Ansible에는 이런 식으로 주로 많이 쓰이는 프로그램들에 대해서 다룰 수 있는 모듈을 제공한다.


순서대로 설명하면 mysql을 실행하고, 파이썬 mysql 패키지를 설치하고, DB를 만들고, DB 사용자를 만들고, 샘플 데이터 (위의 Packer Script에서 복사했던) 가 담겨있는 dump.sql을 실행하고, php mysql 확장을 설치한다.


db.php, dump.sql, aws_playbook_2.yml을 만들고 Packer Script의 Provisioner 부분에 아까 봤던 코드를 넣어주면 준비 완료.

다시 Packer Script를 실행해서 이미지를 만들고 그 이미지로 VM을 생성하는 프로그램을 동작시키자.


http://xxx.xxx.xxx.xxx/db.php로 접속했을 때 Hello World!! 가 뜨는 것을 확인할 수 있을 것이다.


4) 통합하기


처음 약속했던대로 프로그램 한번 실행만으로 이 모든 작업을 마칠 수 있도록 프로그램을 짜자.


별건 없고 2) 의 파이썬 프로그램에다가 Packer를 실행하는 코드를 넣어주고, API 다룰 때 콘솔에서 수동으로 가져왔던 몇몇 부분들까지 마저 자동화해주면 될 것 같다.

Packer, Ansible로 하는 작업들은 각 스크립트에 따로 다 작성해놨고, Ansible도 Packer Provisioner를 통해서 실행되니까 결과적으로


Packer 스크립트 실행 -> (OS랑 소프트웨어 스택까지 설치된 이미지가 생성) -> libcloud API를 이용해 이 이미지로 VM 생성


의 과정이 된다.


from libcloud.compute.types import Provider

from libcloud.compute.providers import get_driver

import subprocess


ACCESS_ID = 'XXXXXXXXXXXXXXXXXXX'

SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXX'

SIZE_ID = 't1.micro'

IMAGE_NAME = 'packer-test'


cmd = r'packer build C:\Users\user\Desktop\packer\aws_example.json'


try:

    subprocess.check_output(cmd,shell=True,stderr=subprocess.STDOUT)

except subprocess.CalledProcessError as e:

    raise RuntimeError("command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output))


print("Packer build finished.")


cls = get_driver(Provider.EC2)

driver = cls(ACCESS_ID, SECRET_KEY, region="us-east-1")


driver.create_key_pair('packer-test-keypair')

driver.ex_authorize_security_group_permissive('default')


sizes = driver.list_sizes()

images = driver.list_images()


size = [s for s in sizes if s.id == SIZE_ID][0]

image = [i for i in images if i.name == IMAGE_NAME][0]


print("Creating VM...")

for i in range(3):

    node = driver.create_node(name='VM-test-'+str(i), image=image, size=size, ex_keyname='packer-test-keypair')

print("Finished.")


- 앞에서는 이미지 ID를 콘솔에서 확인하고 그걸 붙여넣은 다음 list_images에서 이미지를 찾는 방식으로 했는데, 자동화를 위해서 이미지 이름을 미리 지정해주고 찾는 방식으로 했다. 가장 최근에 생성된 사용자 이미지 ID를 가져오는 걸 API로 해보려고 했었는데 잘 안돼서... 그러니까 Packer Script에서도 이미지 이름을 packer-test (뒤에 timestamp 안 붙게) 로 바꿔줘야 한다.


- 키 페어 만드는거랑 보안 그룹 (방화벽) 규칙 추가해주는 걸 위에선 수동으로 했었는데 여기서는 중간에 API를 썼다. 

> 근데 이렇게 키 페어를 만들면 키 페어가 만들어지긴 하는데 비밀 키 다운로드가 안된다... 저 메소드의 리턴값을 뜯어봐도 그런건 안나온다. 비밀 키를 모르면 VM을 만들고 웹 서버에 접속하는데는 문제가 없지만 VM에 SSH로 접속할 수가 없게 되니 

> 보안 그룹 (방화벽) 규칙 만드는 메소드가 여러 개가 있는데 여기서 쓴 건 모든 인바운드 트래픽을 허용하게 만든다. 테스트니까 상관없지만 당연히 실제 환경이라면 이런거 쓰면 안될 것이다. 다른 메소드가 어떤게 있는지는 libcloud 문서를 참고하자...


- packer build를 파이썬에서 실행하기 위해서 subprocess 모듈을 썼다. subprocess.call() 이 가장 기본적이지만 packer build하다가 에러 메시지가 뜰 경우 대처하기 위해서 예외처리 코드를 넣었다.


- packer build 뒤에 절대경로로 써준 이유는 Packer Script의 경로를 확실히 해주기 위함이다. 절대경로로 안 써주니까 못찾더라. 파이썬 working directory랑 저 폴더가 달라서 그런지...? 여튼 절대경로로 쓰다보니 이스케이프 문자 \가 들어가버려서 문자열 앞에 r를 넣었다 (r를 앞에 써주면 이스케이프를 무시한다). 그리고 Packer Script 내에서도 여러 파일 경로들을 절대경로로 바꿔줘야 하더라. Packer Script는 아래와 같다.


{

  "variables": {

    "aws_access_key": "XXXXXXXXXXXXXXXXXXX",

    "aws_secret_key": "XXXXXXXXXXXXXXXXXXXXXXXXXX"

  },

  "builders": [{

    "type": "amazon-ebs",

    "access_key": "{{user `aws_access_key`}}",

    "secret_key": "{{user `aws_secret_key`}}",

    "region": "us-east-1",

    "source_ami_filter": {

      "filters": {

        "virtualization-type": "hvm",

        "name": "ubuntu/images/*ubuntu-xenial-16.04-amd64-server-*",

        "root-device-type": "ebs"

      },

      "owners": ["099720109477"],

      "most_recent": true

    },

    "instance_type": "t2.micro",

    "ssh_username": "ubuntu",

    "ami_name": "packer-test"

  }],

    "provisioners":[

    {

      "type":"shell",

      "execute_command":"echo 'install ansible' | {{ .Vars }} sudo -E -S sh '{{ .Path }}'",

      "inline":[

                "sleep 30",

                "apt-add-repository ppa:rquillo/ansible",

                "/usr/bin/apt-get update", 

                "/usr/bin/apt-get -y install ansible"

                ]

    },

    {

       "type":"ansible-local",

       "playbook_file":"C:\\Users\\user\\Desktop\\packer\\aws_playbook.yml"

    },

{

"type": "shell",

"inline":["sudo chown ubuntu: /var/www/html"]

},

{

"type": "file",

"source": "C:\\Users\\user\\Desktop\\packer\\index.php",

"destination": "/var/www/html/index.php"

},

{

"type": "file",

"source": "C:\\Users\\user\\Desktop\\packer\\db.php",

"destination": "/var/www/html/db.php"

},

{

"type": "file",

"source": "C:\\Users\\user\\Desktop\\packer\\dump.sql",

"destination": "/var/www/html/dump.sql"

},

{

       "type":"ansible-local",

       "playbook_file":"C:\\Users\\user\\Desktop\\packer\\aws_playbook_2.yml"

    }

  ]

 

}


Ansible playbook 두 개의 내용은 3) 에서 봤던 거랑 동일하다.



처음 목표했던대로 파이썬 프로그램 하나 돌려서 LAMP 스택이 설치된 VM 세 대를 만들었다.


다만 코드에 개선의 여지가 많다. 키 페어 만들고 비밀키 다운로드가 안되는 문제도 있었고, Ansible playbook에서 apt 모듈을 쓰면 더 간결할텐데 shell 모듈을 쓰기도 했고, 이미지 이름이랑 키 페어 이름을 하드코딩했기 때문에 두 번 이상 실행하면 중복된 게 존재한다면서 에러가 뜰 것이다. 처음에 그렸던 그림에선 VPC 만든다고 했었는데 귀찮아서 그냥 default로 했다. 그리고 LAMP 스택을 깔기만 했지 각종 설정들은 해주지 않았다.


요즘 관심있는 주제이기 때문에 더 공부해서 확장해볼 생각이다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함