AnsibleでAWSモジュールを使う

みなさんこんにちは。
Chef より Ansible 派、インフラストラクチャー部のぬまっちこと沼沢です。 re:Invent 後ですが、Ansible ネタの投稿です。

今回はバージョンが上がる度に強化される AWS 関連モジュールと、そのサンプルをご紹介したいと思います。

そもそも Ansible とは

構築する台数が1台や2台のうちは手作業での構築で良いのですが、5台、10台…と増えていくと

  • 初回の構築にかかる工数が膨らむ
  • いくら手順化していても人的ミスを引き起こす可能性を秘めている
  • 冗長化しているサーバでは、同じコマンドを何回も実行しなくてはいけない
  • 環境が壊れた等の理由で、再構築が必要な際にまた同じことをしなくてはいけない

というようなことがあるため、工数短縮と作業ミスを減らすために、手作業での構築は減らしたいのです。
特にクラウドが普及してきた昨今では、Immutable Infrastructure の登場などもあり、何度も同じ(もしくは少しだけ違う)構成を立てる機会が増えてきています。

その中で、Chef や Ansible 等の構成管理ツールはサーバ(主にミドルウェア)の構築をコード化するのに役立ちます。

Chef ではなく Ansible 派の理由

単に Ruby が書けないから Chef は対象のサーバにエージェントをインストールする必要がありますが、Ansible はエージェントが不要です。実行する環境に Ansible の準備が整えば、そこから ssh が可能なサーバに対して実行することができます。

実行内容は YAML 形式で記述するため、Ruby が書けない方は入りやすいと思います。

※これは全て執筆者の主観的なものであり、mediba の公式見解ではございません。

Ansible 2.2 時点で使える AWS 関連モジュール

Ansible は、通常 OS のミドルウェアのセットアップなどに利用されますが、AWS の各サービスの構築にも利用可能です。
本ブログ執筆時点で 87個 もの AWS 関連モジュールが用意されています。
夢が広がりますね。
Cloud Modules — Ansible Documentation

AWS サービス単位で列挙すると以下の通りです。

  • CloudFormation
  • CloudTrail
  • CloudWatch Events
  • DynamoDB
  • EC2
  • ECS
  • EFS
  • ElastiCache
  • Lambda
  • IAM
  • Kinesis
  • RDS
  • Redshift
  • Route 53
  • S3
  • SNS
  • SQS
  • STS

上記の18サービスに対応しています。
これだけで大抵の環境はコード化できるのではないでしょうか。

サンプル: EC2 インスタンスを立てる Ansilbe

サンプルとして、EC2 インスタンスを起動する Ansible を書いてみました。
本サンプルは以下の要件で動作します。

  • ansible 2.2.0.0 (恐らく 2.0 以降で OK)
  • boto 2.43.0
  • boto3 1.4.1
  • botocore 1.4.65

構成としてはこのイメージで作っています。

Ansible がやっていることは以下の3つです。

  • VPC 構築
  • Security Group 作成
  • EC2 Launch

ディレクトリ構造は以下の通りです。

$ tree
.
├── build_aws.yml
└── roles
    ├── ec2
    │   └── tasks
    │       └── main.yml
    ├── securitygroup
    │   └── tasks
    │       └── main.yml
    └── vpc
        └── tasks
            └── main.yml

ではまず build_aws.yml の中身です。

- hosts: localhost
  connection: local
  roles:
    - vpc
    - securitygroup
    - ec2
  • hosts: localhost, connection: local とすることで、どこにも接続せずローカルで実行することを意味します。

次に roles/vpc/tasks/main.yml の中身です。

- name: VPC, Public Subnet, Private Subnet 構築
  ec2_vpc:
    profile: "{{ profile_name }}"
    state: present
    cidr_block: "10.0.0.0/16"
    region: ap-northeast-1
    resource_tags: { "Name": "sample-vpc" }
    internet_gateway: True
    subnets:
      - cidr: "10.0.0.0/24"
        az: ap-northeast-1a
        resource_tags: { "Name": "sample-subnet-a" }
      - cidr: "10.0.1.0/24"
        az: ap-northeast-1c
        resource_tags: { "Name": "sample-subnet-c" }
  register: vpc_info

- name: Public Subnet に InternetGateway を設定
  ec2_vpc_route_table:
    profile: "{{ profile_name }}"
    region: ap-northeast-1
    state: present
    lookup: tag
    vpc_id: "{{ vpc_info.vpc_id }}"
    subnets:
      - "10.0.0.0/24"
      - "10.0.1.0/24"
    routes:
      - dest: 0.0.0.0/0
        gateway_id: "{{ vpc_info.igw_id }}"
    tags:
      Name: "sample-public-rtb"
  • ec2_vpc モジュールで、10.0.0.0/16 の CIDR ブロックを持つ VPC を構築しています。
  • ec2_vpc モジュール内で、Subnet も同時に定義し、作成しています。
  • 最後に、作成した Subnet に Internet Gateway を設定して、グローバルに通信できるようにしています。

次に、roles/securitygroup/tasks/main.yml の中身です。

- name: Security Group 作成先の VPC ID 取得
  shell: >
    aws ec2 describe-vpcs \
      --region ap-northeast-1 \
      --filters Name=tag:Name,Values="sample-vpc" \
      --query 'Vpcs[0].VpcId' \
      --output text \
      --profile {{ profile_name }}
  changed_when: False
  register: vpc_id

- name: Security Group 作成
  ec2_group:
    profile: "{{ profile_name }}"
    state: present
    name: "sample-ec2-sg"
    description: "ec2 security group"
    vpc_id: "{{ vpc_id.stdout }}"
    region: ap-northeast-1
    rules:
      - proto: tcp
        from_port: 22
        to_port: 22
        cidr_ip: "0.0.0.0/0"
  • Security Group は VPC に紐付くため、その VPC ID を取得し、ec2_group モジュールで Security Group を作成しています。

最後に、roles/ec2/tasks/main.yml の中身です。

- name: EC2 のキーペア発行
  ec2_key:
    profile: "{{ profile_name }}"
    region: ap-northeast-1
    state: present
    wait: yes
    wait_timeout: 300
    name: "sample-ec2-key"

- name: Subnet ID 取得
  shell: >
    aws ec2 describe-subnets \
      --region ap-northeast-1 \
      --filters Name=tag:Name,Values="sample-subnet-a" \
      --query 'Subnets[0].SubnetId' \
      --output text \
      --profile {{ profile_name }}
  changed_when: False
  register: sub_id

- name: EC2 インスタンス作成
  ec2:
    profile: "{{ profile_name }}"
    region: ap-northeast-1
    vpc_subnet_id: "{{ sub_id.stdout }}"
    state: present
    image: ami-0c11b26d
    instance_type: t2.micro
    count: 1
    key_name: sample-ec2-key
    volumes:
      - device_name: /dev/xvda
        volume_type: gp2
        volume_size: 8
        delete_on_termination: true
    group: sample-ec2-sg
    termination_protection: no
    instance_tags:
      Name: "sample-ec2"
  register: ec2

- name: EIP 付与
  ec2_eip:
    profile: "{{ profile_name }}"
    region: ap-northeast-1
    device_id: "{{ ec2.instances[0].id }}"
  • まずは ec2_key モジュールを使用して EC2 のキーペアを作成しています。
  • インスタンス作成対象の Subnet ID を取得し、ec2 モジュールを使用してインスタンスを作成しています。
  • 最後に、ec2_eip を使用して、作成した EC2 インスタンスに EIP を付与しています。

これらの準備が整ったら、以下コマンドで ansible-playbook を実行します。

$ ansible-playbook -i localhost build_aws.yml --extra-vars="profile_name=xxxx"

-i は Inventory ファイルの指定をするオプションで、通常は ansible の実行対象を定義したファイルを指定しますが、AWS の場合は localhost を指定して、ローカルで実行するようにします。

また、–extra-vars で profile 名を指定することで、同じ構成を複数の環境に適用することができます。

これで数分待つと、付与された EIP に対して SSH が可能な EC2 インスタンスが作成されます。

まとめ

いかがでしたでしょうか。サンプルだけ見ると手動で構築した方が遥かに簡単に見えますが、これがさらに巨大な規模のシステムで、何度も作り直す必要があるとなった時にはやってられませんよね。
ぜひ Ansible を活用して AWS の構築から OS のプロビジョニングまで、全てをコード化してしまいましょう。