Tự động tạo, xóa instance AWS sử dụng ansible

Giới thiệu

Lâu lắm rồi mới viết bài lại, tính ra chắc hơn nửa năm rồi @@~, từ ngày mình nhảy việc đến giờ.

Do lười thôi chứ không phải không có thời gian. Nay muốn tập trung viết lại cho thỏa mãn sở thích cũng như note lại cho khỏi quên kiến thức. Haha!!!
Bài viết này mình sẽ giới thiệu đến các bạn một cách để tự động tạo hoặc xóa instance (instance: Bạn có thể hiểu như một VM hoặc VPS cũng được) trên AWS sử dụng ansible nhen.

Thao tác

Bước 1: Cài đặt các gói thư viện cần thiết

Chúng ta sẽ cần boto package cho python trên máy đã cài ansible. Boto là SDK (Software Develoment Kit) được thiết kế để chuyển đổi phản hồi API trên AWS đến python class.

mình dùng Ubuntu và cài boto3, nên sẽ cài đặt như sau:

sudo apt install python3
sudo apt install python3-pip
pip3 install boto boto3

Bước 2: Tạo Ansible vault để lưu access và secrect key AWS

Để lấy được access và secrect key, trên AWS dashboard =>> Service =>> IAM =>> Get access and secret key.

Quay lại trên máy đã cài ansible của mình, tạo file vault để chứa thông tin 2 key này, mục đích của file vault trên ansible là gì? nó sẽ mã hóa nội dung trong file nhạy cảm mà bạn không muốn ai đọc được, trong trường hợp này cặp key mình có được quan trọng vì ai đó biết được họ có thể dùng để thao tác được trên tài khoản AWS của mình.

ansible-vault create vars/asw_keys.yml
New Vault password:
Confirm New Vault password:

Trong nội dung bài viết này mình sẽ không đề cập đến cấu trúc thư mục và file trong ansible, mình sẽ nói đến trong nội dung khác, đại khái để một playbook của ansible chạy được, bạn cần phải có những file, thư mục như là: hosts, main.yml, thư mục vars, thư mục roles …

Bạn nhập mật khẩu vào phần vault yêu cầu, file này sẽ được mở ra, nhập vào nội dung sau:

aws_access_key: AAAAAAABBBBBBBB
aws_secret_key: asdasfsfsdfdfg+asdasgfdhgjhkjhrvdrg

có thể hiểu mình sẽ để các biến theo kiểu key:value trong thư mục vars. Nếu bạn không muốn phải nhập password cho vault mỗi khi chạy playbook, bạn có thể làm như sau:

openssl rand -base64 2048 > vault.pass
ansible-vault create vars/aws_keys.yml --vault-password-file vault.pass

Khi chạy playbook chỉ cần chỉ định file chứa pasword cho vault là được:

ansible-playbook playbook.yml --vault-password-file vault.pass

Để edit file vault:

ansible-vault edit group_vars/all/pass.yml
Vault password:

Bước 3: Tạo các variables cần thiết

vim vars/all
instance_type: t3.micro
security_group_id: sg-5qqwet2241o23
image: ami-0cc293023f983ed53
keypair: privatekey
region: eu-central-1
subnet: subnet-1kknk12n4k12n4442n ##LAN-public
#subnet: subnet-0fasfnsjdfq242144j1n ##LAN-private
count: 1

những thông tin này bạn sẽ lấy được trên AWS dashboard hết:

instance_type: Là cấu hình instance mà bạn muốn tạo.

security_group_id: Chọn policy cho group

image: Giống như bạn chọn CentOS hay Ubuntu vậy.

keypair: private key để mình có thể ssh sau khi tạo xong instance, mặc định user là ec2-user.

region: Location bạn chỉ định để tạo.

subnet: Bạn quy định instance nằm trong subnet nào mà trước đó bạn đã tạo.

count: số lượng instance bạn muốn tạo tuơng tự.

Bước 4: Tạo role provision (role để khởi tạo instance)

vim roles/Provision_Instance/tasks/main.yml
---
- name: Provison EC2 instance
  ec2:
    aws_access_key: '{{ aws_access_key }}'
    aws_secret_key: '{{ aws_secret_key }}'
    key_name: '{{ keypair }}'
    group_id: '{{ security_group_id }}'
    instance_type: '{{ instance_type }}'
    image: '{{ image }}'
    region: '{{ region }}'
    vpc_subnet_id: '{{ subnet }}'
    private_ip: "10.17.100.115"
    count: '{{ count }}'
    wait: yes
    instance_tags:
        Name: awseu-web-1
    volumes:
      - device_name: /dev/xvda
        volume_type: gp2
        #volume_type: io1
        #iops: 400
        volume_size: 50
        delete_on_termination: true
  register: ec2instance

- debug: var=ec2instance

Có một số chổ mình cần giải thích nhỉ?

Thằng ansible nó hỗ trợ module ec2 để bạn tạo instance, bạn đưa các biến mà khi nãy quy định trong thư mục variables vào. Riêng một số biến bạn cần chỉ định cụ thể thì mình sẽ diễn giải trong đoạn này.

private_ip: IP private mà bạn muốn gán cho instance.

instance_tags: mình gán tên cho instance.

volumes: quy định disk khởi tạo và dạng disk mình muốn, có 3 loại: HDD, SSD và I/O SSD.

Bước 5: Tạo role destroy instance

vim roles/Destroy_Instance/main.yml
---
- name: Gather EC2 facts
  ec2_instance_facts:
    region: "{{ region }}"
    filters:
      "tag:Name": "awseu-web-1"
    aws_access_key: "{{ aws_access_key }}"
    aws_secret_key: "{{ aws_secret_key }}"
  register: ec2

- debug: var=ec2.instances

- name: Terminate EC2 instance
  ec2:
    instance_ids: '{{ item.instance_id }}'
    state: absent
    region: "{{ region }}"
    aws_access_key: "{{ aws_access_key }}"
    aws_secret_key: "{{ aws_secret_key }}"
  with_items: "{{ ec2.instances }}"

Ở đây có 2 bước mình cần xử lý:

Bước 6: Tạo main file để run playbook

Gần xong rồi đấy, bây giờ bạn cần tạo file main.yml, file hosts nữa là được.

Đối với file main.yml (mình đặt tên managed-instances.yml):

---
- hosts: local
  connection: local
  gather_facts: False
  vars:
    ansible_python_interpreter: /usr/bin/python3
  vars_files:
    - vars/aws_keys.yml
    - vars/all

  roles:
    - Provision_Instance
    #- Destroy_Instance

Khi khởi tạo instance thì comment (#) Destroy_Instance và ngược lại.

Đối với file host:

[local]
localhost

Xong rồi đó, run playbook với cú pháp như sau:

sudo ansible-playbook -i hosts --ask-vault-pass managed-instances.yml

Nhập password vault vào, khi khởi tạo instance kết quả trả về như sau:

tritran@cinatic:~/Desktop/Ansible/AWS/AWS-Managed_instance$ ansible-playbook managed-instances.yml -i hosts --ask-vault-pass
Vault password: 

PLAY [local] ********************************************************************************************************************************************************************************************************

TASK [Provision_Instance : Provison EC2 instance] *******************************************************************************************************************************************************************
changed: [localhost]

TASK [Provision_Instance : debug] ***********************************************************************************************************************************************************************************
ok: [localhost] => {
    "ec2instance": {
        "changed": true, 
        "failed": false, 
        "instance_ids": [
            "i-0c79943c90ea5a878"
        ], 
        "instances": [
            {
                "ami_launch_index": "0", 
                "architecture": "x86_64", 
                "block_device_mapping": {
                    "/dev/xvda": {
                        "delete_on_termination": true, 
                        "status": "attached", 
                        "volume_id": "vol-019e3a93f209f52f4"
                    }
                }, 
                "dns_name": "", 
                "ebs_optimized": false, 
                "groups": {
                    "sg-0e9f7f6ebc633f0db": "launch-wizard-1"
                }, 
                "hypervisor": "xen", 
                "id": "i-0c79943c90ea5a878", 
                "image_id": "ami-0cc293023f983ed53", 
                "instance_type": "t2.micro", 
                "kernel": null, 
                "key_name": "ebuynow", 
                "launch_time": "2019-10-19T01:49:34.000Z", 
                "placement": "eu-central-1a", 
                "private_dns_name": "ip-172-17-1-25.eu-central-1.compute.internal", 
                "private_ip": "172.17.1.25", 
                "public_dns_name": "", 
                "public_ip": null, 
                "ramdisk": null, 
                "region": "eu-central-1", 
                "root_device_name": "/dev/xvda", 
                "root_device_type": "ebs", 
                "state": "running", 
                "state_code": 16, 
                "tags": {
                    "Name": "awseu-test-1"
                }, 
                "tenancy": "default", 
                "virtualization_type": "hvm"
            }
        ], 
        "tagged_instances": []
    }
}

PLAY RECAP **********************************************************************************************************************************************************************************************************
localhost                  : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Provision thành công rồi nè

Còn đây là kết quả khi destroy 1 instance nào đó:

tritran@cinatic:~/Desktop/Ansible/AWS/AWS-Managed_instance$ ansible-playbook managed-instances.yml -i hosts --ask-vault-pass
Vault password: 

PLAY [local] ********************************************************************************************************************************************************************************************************

TASK [Destroy_Instance : Gather EC2 facts] **************************************************************************************************************************************************************************
ok: [localhost]

TASK [Destroy_Instance : debug] *************************************************************************************************************************************************************************************
ok: [localhost] => {
    "ec2.instances": [
        {
            "ami_launch_index": 0, 
            "architecture": "x86_64", 
            "block_device_mappings": [
                {
                    "device_name": "/dev/xvda", 
                    "ebs": {
                        "attach_time": "2019-10-19T01:49:35+00:00", 
                        "delete_on_termination": true, 
                        "status": "attached", 
                        "volume_id": "vol-019e3a93f209f52f4"
                    }
                }
            ], 
            "capacity_reservation_specification": {
                "capacity_reservation_preference": "open"
            }, 
            "client_token": "", 
            "cpu_options": {
                "core_count": 1, 
                "threads_per_core": 1
            }, 
            "ebs_optimized": false, 
            "ena_support": true, 
            "hibernation_options": {
                "configured": false
            }, 
            "hypervisor": "xen", 
            "image_id": "ami-0cc293023f983ed53", 
            "instance_id": "i-0c79943c90ea5a878", 
            "instance_type": "t2.micro", 
            "key_name": "ebuynow", 
            "launch_time": "2019-10-19T01:49:34+00:00", 
            "monitoring": {
                "state": "disabled"
            }, 
            "network_interfaces": [
                {
                    "attachment": {
                        "attach_time": "2019-10-19T01:49:34+00:00", 
                        "attachment_id": "eni-attach-0bc3baceb808158e4", 
                        "delete_on_termination": true, 
                        "device_index": 0, 
                        "status": "attached"
                    }, 
                    "description": "", 
                    "groups": [
                        {
                            "group_id": "sg-0e9f7f6ebc633f0db", 
                            "group_name": "launch-wizard-1"
                        }
                    ], 
                    "interface_type": "interface", 
                    "ipv6_addresses": [], 
                    "mac_address": "02:01:a3:99:44:10", 
                    "network_interface_id": "eni-0f691aaf3a5696463", 
                    "owner_id": "710235054459", 
                    "private_dns_name": "ip-172-17-1-25.eu-central-1.compute.internal", 
                    "private_ip_address": "172.17.1.25", 
                    "private_ip_addresses": [
                        {
                            "primary": true, 
                            "private_dns_name": "ip-172-17-1-25.eu-central-1.compute.internal", 
                            "private_ip_address": "172.17.1.25"
                        }
                    ], 
                    "source_dest_check": true, 
                    "status": "in-use", 
                    "subnet_id": "subnet-075ed2430e055fb28", 
                    "vpc_id": "vpc-f476929e"
                }
            ], 
            "placement": {
                "availability_zone": "eu-central-1a", 
                "group_name": "", 
                "tenancy": "default"
            }, 
            "private_dns_name": "ip-172-17-1-25.eu-central-1.compute.internal", 
            "private_ip_address": "172.17.1.25", 
            "product_codes": [], 
            "public_dns_name": "", 
            "root_device_name": "/dev/xvda", 
            "root_device_type": "ebs", 
            "security_groups": [
                {
                    "group_id": "sg-0e9f7f6ebc633f0db", 
                    "group_name": "launch-wizard-1"
                }
            ], 
            "source_dest_check": true, 
            "state": {
                "code": 16, 
                "name": "running"
            }, 
            "state_transition_reason": "", 
            "subnet_id": "subnet-075ed2430e055fb28", 
            "tags": {
                "Name": "awseu-test-1"
            }, 
            "virtualization_type": "hvm", 
            "vpc_id": "vpc-f476929e"
        }
    ]
}

TASK [Destroy_Instance : Terminate EC2 instance] ********************************************************************************************************************************************************************
changed: [localhost] => (item={u'root_device_type': u'ebs', u'private_dns_name': u'ip-172-17-1-25.eu-central-1.compute.internal', u'cpu_options': {u'threads_per_core': 1, u'core_count': 1}, u'security_groups': [{u'group_id': u'sg-0e9f7f6ebc633f0db', u'group_name': u'launch-wizard-1'}], u'monitoring': {u'state': u'disabled'}, u'subnet_id': u'subnet-075ed2430e055fb28', u'ebs_optimized': False, u'state': {u'code': 16, u'name': u'running'}, u'source_dest_check': True, u'client_token': u'', u'virtualization_type': u'hvm', u'root_device_name': u'/dev/xvda', u'tags': {u'Name': u'awseu-test-1'}, u'key_name': u'ebuynow', u'image_id': u'ami-0cc293023f983ed53', u'ena_support': True, u'hibernation_options': {u'configured': False}, u'capacity_reservation_specification': {u'capacity_reservation_preference': u'open'}, u'public_dns_name': u'', u'block_device_mappings': [{u'ebs': {u'status': u'attached', u'delete_on_termination': True, u'attach_time': u'2019-10-19T01:49:35+00:00', u'volume_id': u'vol-019e3a93f209f52f4'}, u'device_name': u'/dev/xvda'}], u'placement': {u'group_name': u'', u'tenancy': u'default', u'availability_zone': u'eu-central-1a'}, u'ami_launch_index': 0, u'hypervisor': u'xen', u'network_interfaces': [{u'status': u'in-use', u'description': u'', u'subnet_id': u'subnet-075ed2430e055fb28', u'interface_type': u'interface', u'ipv6_addresses': [], u'network_interface_id': u'eni-0f691aaf3a5696463', u'private_dns_name': u'ip-172-17-1-25.eu-central-1.compute.internal', u'attachment': {u'status': u'attached', u'device_index': 0, u'attachment_id': u'eni-attach-0bc3baceb808158e4', u'delete_on_termination': True, u'attach_time': u'2019-10-19T01:49:34+00:00'}, u'private_ip_addresses': [{u'private_ip_address': u'172.17.1.25', u'primary': True, u'private_dns_name': u'ip-172-17-1-25.eu-central-1.compute.internal'}], u'mac_address': u'02:01:a3:99:44:10', u'private_ip_address': u'172.17.1.25', u'vpc_id': u'vpc-f476929e', u'groups': [{u'group_id': u'sg-0e9f7f6ebc633f0db', u'group_name': u'launch-wizard-1'}], u'source_dest_check': True, u'owner_id': u'710235054459'}], u'launch_time': u'2019-10-19T01:49:34+00:00', u'instance_id': u'i-0c79943c90ea5a878', u'instance_type': u't2.micro', u'architecture': u'x86_64', u'state_transition_reason': u'', u'private_ip_address': u'172.17.1.25', u'vpc_id': u'vpc-f476929e', u'product_codes': []})

PLAY RECAP **********************************************************************************************************************************************************************************************************
localhost                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Đã Destroy instance

Tổng kết

Mình đã hoàn tất hướng dẫn Tự động tạo, xóa instance sử dụng ansible, nhìn chung thì playbook này có ưu điểm là setup được instance nhanh chóng thay vì vào giao diên AWS click các kiểu, nhược điểm là chưa tạo ra multi instance khác loại được, vì nếu bạn muốn tạo nhiều hơn 1 instance thì nó vẫn ở  1 loại type mà bạn quy định trong variables. Hy vọng tuơng lai mình siêng hơn để cải thiện tính năng 😀
Cám ơn các bạn đã theo dõi!