お久しぶりです。サイオステクノロジー OSS サポート担当の鎌田です。
最近、業務で使用する PC が Windows 機から Linux 機に変わり、だいぶいい感じに仕事ができるようになりました。このあたりについては、改めてブログ記事にしていこうと思っています。
さて、当社では OSS よろず相談室をご契約いただいているお客様向けに定期的に勉強会を開催しています。当社のエンジニアが講師としてお客様の方々に有用な情報をお伝えできるような勉強会を企画、開催しているのですが、この中にハンズオン形式での勉強会も開催することがあります。
基本的に、環境は当社で用意したものをご利用いただくことがあるのですが、この環境の用意というのがわりと時間がかかるもので、クラウドを使うにしても手でひとつひとつ作っていたのでは時間がかかりすぎてしまいます。勉強会の開催前に動作確認をしていると問題を見つけて再構築、ということもよくあるので、環境の用意や破棄はできるだけ簡単にしたいですよね。
というわけで、今回はその勉強会用の環境を作ったり捨てたりする上で Terraform と Ansible で自動化を試みた話を書いていきたいと思います。
Terraform とは
すでに皆さんご存知かと思いますが、Terraform は HashiCorp 社が開発しているインフラをコード化するためのツールです。各種クラウドに対応していて、すでに活用されている方も多いかと思います。
Ansible にも各種クラウドに対応したモジュールは存在しますが、使い方が少し難しかったり、複数の EC2 インスタンスを一気に作りたくても 1つ 1つ作り始めて構築に時間がかかったりと、今ひとつ痒いところに手が届かない感じです。
ただ、Ansible には terraform モジュールというのが存在するので、AWS の環境構築部分を Terraform を使うよう分離して、Ansible から Terraform を呼ぶようにすればわりといい感じになりそうです。
それではさっそく、やっていきましょう。
まずは要件を定義する
環境を構築するにも、まずは要件を定義しないといけないですよね。今回はハンズオン形式の勉強会で、想定受講者数は 10 人、開催場所は国内です。OS は CentOS 7 を想定していて、講義内容として HTTP を使ったある OSS の講義とすると、ざっと以下のような要件になります。
- リージョンは ap-northeast-1。
- インバウンドとして SSH / HTTP、アウトバウンドは yum を使うので HTTP(S) だけあれば良い。
- EC2 インスタンス数は 10 個。重たい OSS ではないので t3.small あたりで十分。
Terrafrom でリソース定義
要件も決まったので、さっそく Terraform でリソースを定義してみます。今回は Ansible から Terraform を呼ぶため、Ansible の Playbook 等を置く場所にひとつディレクトリを作成して、そこに Terraform の定義を置いていこうと思います。
ディレクトリ構成
playbook/ +- create.yml (リソース作成時の Playbook) +- destroy.yml (リソース破棄時の Playbook) +- terraform/ +- main.tf (Terraform のリソース定義)
今回作成した Terraform の定義は次のとおりです。
provider "aws" { region = "ap-northeast-1" } resource "aws_security_group" "sg-handson" { name = "handson" description = "handson" ingress = { from_port = 22 to_port = 22 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] ipv6_cidr_blocks = ["::/0"] } ingress = { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] ipv6_cidr_blocks = ["::/0"] } egress = { from_port = 80 to_port = 80 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] ipv6_cidr_blocks = ["::/0"] } egress = { from_port = 443 to_port = 443 protocol = "tcp" cidr_blocks = ["0.0.0.0/0"] ipv6_cidr_blocks = ["::/0"] } } resource "aws_instance" "ec2-handson-student" { count = 10 ami = "ami-045f38c93733dd48d" instance_type = "t3.small" key_name = "mykey" vpc_security_group_ids = ["${aws_security_group.sg-handson.id}"] associate_public_ip_address = "true" root_block_device = { delete_on_termination = "true" } credit_specification { cpu_credits = "standard" } tags { Name = "${format("handson-student-%02d", count.index + 1)}" } }
最後の EC2 インスタンス定義のタグ定義で format を使って EC2 個別に名前をつけてみました。これで、インスタンスごとに「handson-student-01」「handson-student-02」…のように名前がつけられます。この名前を、あとで Ansible でホスト名の設定に使ってみます。
Ansible の Playbook を書く
次に、Ansible の Playbook を書きます。まずは Terraform を読んでリソースを作ってもらい、次に Ansible の Dynamic Inventory 機能を使ってインベントリを作って、それをもとに処理していきます。
まずは、localhost 上で動かすタスクを定義していきます。
- hosts: localhost connection: local gather_facts: no tasks: - name: Create resources by Terraform terraform: project_path: 'terraform/' state: present register: terraform - name: Execute EC2 dynamic inventory script script: inventory/ec2.py --refresh-cache when: terraform.changed - name: Refresh inventory meta: refresh_inventory - name: Waiting for upcoming ssh wait_for: host: "{{ item }}" port: 22 state: started loop: "{{ groups['security_group_topix20190301'] }}"
EC2 の Dynamic Inventory を使うことで、AWS のセキュリティグループを Ansible のグループとしても使えるので、EC2 の SSH アクセス待ちの wait_for モジュールをセキュリティグループ名を使って回すことができます。
次に、EC2 インスタンスを作ったあとに、それぞれのインスタンスで実行するタスクを定義していきます。
- hosts: security_group_handson remote_user: centos become: True tasks: - name: Assign hostname hostname: name: "{{ ec2_tag_Name }}" - name: Update to latest yum: name: '*' state: latest - name: Add ssh public key authorized_key: user: centos key: "{{ lookup('file', 'sshkey.pub') }}" state: present
ホスト変数に EC2 のタグも定義されているので、ec2_tag_Name のようにタグの値を拾ってホスト名を設定することができます。最後に、パッケージを最新の状態にして、勉強会当日に受講者に配布する SSH 鍵の公開鍵を登録して終了です。
後片付け用のタスクも定義
勉強会が終わったあとの後片付けも簡単です。Terraform の機能を使えばいいだけなので、次のような Playbook になります。
- hosts: localhost connection: local gather_facts: no tasks: - name: Destroy resources by Terraform terraform: project_path: 'terraform/' state: absent
まとめ
Ansible だけで AWS リソースを定義していこうとすると大変ですが、Terraform と組み合わせることでリソース定義がやりやすくなるばかりか、後片付けも Terraform の機能を使うことができるので、コマンド一発でリソースの作成・破棄ができるのは便利ということがわかりました。