ECSにZabbix環境をデプロイする【cloudpack大阪ブログ】

cloudpack大阪の佐々木です。
Zabbix環境をコンテナとしてECS上につくってみたので、まとめときます。

デプロイ方法

ecs-cliとdocker-compose使ってデプロイします。

基本的なやり方はこちら
http://qiita.com/taishin/items/076a7699c787da68e396

docker-coposeはv2が使えるらしいので、今回はv2で記述しています。
http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/cmd-ecs-cli-compose.html

Amazon ECS CLI の最新バージョンでは、Docker 構成ファイル構文のバージョン 1 と 2 がサポートされています。

v2のリファレンス
https://docs.docker.com/compose/compose-file/compose-file-v2/

docker-compose.yml

docker-compose.yml
version: '2'

services:
  mysql-server:
    image: mysql
    mem_limit: 268435456
    environment:
      - MYSQL_DATABASE=zabbix
      - MYSQL_USER=zabbix
      - MYSQL_PASSWORD=zabbix
      - MYSQL_ROOT_PASSWORD=zabbix
    volumes:
        - /ecs/zabbix_mysql:/var/lib/mysql
    logging:
      driver: awslogs
      options:
        awslogs-group: "ECS-zabbix"
        awslogs-region: "us-east-1"
        awslogs-stream-prefix: "zabbix"

  zabbix-server-mysql:
    image: zabbix/zabbix-server-mysql:ubuntu-3.2-latest
    mem_limit: 134217728
    environment:
      - DB_SERVER_HOST=mysql-server
      - MYSQL_USER=zabbix
      - MYSQL_PASSWORD=zabbix
      - MYSQL_DATABASE=zabbix
    logging:
      driver: awslogs
      options:
        awslogs-group: "ECS-zabbix"
        awslogs-region: "us-east-1"
        awslogs-stream-prefix: "zabbix"
    links:
      - mysql-server
    ports:
      - 10050:10050

  zabbix-web-nginx-mysql:
    image: taishin/zabbix-web:latest
    mem_limit: 134217728
    environment:
      - DB_SERVER_HOST=mysql-server
      - ZBX_SERVER_HOST=zabbix-server-mysql
      - MYSQL_USER=zabbix
      - MYSQL_PASSWORD=zabbix
      - MYSQL_DATABASE=zabbix
      - TZ=Asia/Tokyo
      - COMPOSE_HTTP_TIMEOUT=200
    logging:
      driver: awslogs
      options:
        awslogs-group: "ECS-zabbix"
        awslogs-region: "us-east-1"
        awslogs-stream-prefix: "zabbix"
    links:
      - mysql-server
      - zabbix-server-mysql
    ports:
      - 8090:80

順番に説明します。

コンテナ

下記の3つで構成するようにしました。
– mysql
– zabbix-server
– zabbix-web

Zabbixのコンテナはこちらのを使用します。
https://hub.docker.com/u/zabbix/

zabbix-webはそのまま使うとグラフの日本語が文字化けしますので、日本語フォントを入れて、Buildします。
Dokcerfileは下記のようになります。

FROM zabbix/zabbix-web-nginx-mysql:ubuntu-3.2-latest
RUN apt-get update -y && apt-get install -y fonts-ipafont
RUN ln -s /usr/share/fonts/opentype/ipafont-gothic/ipagp.ttf /usr/share/zabbix/fonts/ipagp.ttf
RUN sed -i -e 's/graphfont/ipagp/g' /usr/share/zabbix/include/defines.inc.php

ecs-cliで使うdocker-composeはbuildに対応していないので、あらかじめビルドしてどこかにアップしておく必要があります。
http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/cmd-ecs-cli-compose.html

重要
build ディレクティブは現在サポートされていません。

今回は自分のdockerhubにアップしておきました。

メモリ制限

メモリ制限は下記に記述でハード制限ができます。ソフト制限はできないっぽいです。

    mem_limit: 268435456

永続化データ

MySQLのデータは永続化したいので、ボリュームを作成します。
下記の記述でホストOSの/etc/zabbix_mysqlがコンテナの/var/lib/mysqlにマウントされます。

    volumes:
        - /ecs/zabbix_mysql:/var/lib/mysql

マネジメントコンソールで見るとこんな感じです。

Kobito.NcjJB2.png

Kobito.dg0AbK.png

amazon-ecs-optimizedのAMIを使う場合は下記も注意です。
http://qiita.com/taishin/items/cff9c3212d0f697653e4

Log

コンテナが出力するログはCloudwatch Logsに転送します。
http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/using_awslogs.html

docker-compose v1のときは

log_driver: awslogs

こんな記述でしたが、v2では下記のような表記になります。

    logging:
      driver: awslogs
      options:
        awslogs-group: "ECS-zabbix"
        awslogs-region: "us-east-1"
        awslogs-stream-prefix: "zabbix"

ロググループはあらかじめ作成しておく必要があります。

ストリームプレフィックスですが、

  • awslogs-stream-prefix をつけない場合

Kobito.V9TWgn.png

コンテナごとに乱数のストリーム名になります。

  • awslogs-stream-prefix をつけた場合
    Kobito.GSxPru.png

ストリーム名が下記のフォーマットになるので、付けておいた方が分かりやすいです。

prefix-name/container-name/ecs-task-id

ポート

ホストの8090番ポートをコンテナの80番ポートに転送しています。

    ports:
      - 8090:80

ちなみに固定ポートではなく、ダイナミックポートにする場合は下記のように書きます。

    ports:
      - :80

デプロイ

ecs-cli compose service up です。

$ ecs-cli compose service up                                                  [10:47:02]
WARN[0000] Skipping unsupported YAML option...           option name=networks
WARN[0000] Skipping unsupported YAML option for service...  option name=networks service name=zabbix-server-mysql
WARN[0000] Skipping unsupported YAML option for service...  option name=networks service name=zabbix-web-nginx-mysql
WARN[0000] Skipping unsupported YAML option for service...  option name=networks service name=mysql-server
INFO[0001] Using ECS task definition                     TaskDefinition="ecscompose-zabbix-docker:40"
INFO[0002] Created an ECS service                        service=ecscompose-service-zabbix-docker taskDefinition="ecscompose-zabbix-docker:40"
INFO[0002] Updated ECS service successfully              desiredCount=1 serviceName=ecscompose-service-zabbix-docker
INFO[0003] Describe ECS Service status                   desiredCount=1 runningCount=0 serviceName=ecscompose-service-zabbix-docker
INFO[0018] ECS Service has reached a stable state        desiredCount=1 runningCount=1 serviceName=ecscompose-service-zabbix-docker

マネージメントコンソールでタスク定義をいじるようり、docker-composeの方がだいぶ楽ですね。

続きを読む

Amazon RDSのMySQLバイナリログ保持時間を設定する。

はじめに

Amazon RDS では、 mysql.rds_set_configuration ストアドプロシージャを使用してバイナリログの保持時間を指定することができます。

設定

それでは保持時間について確認してみます。

保持時間確認
mysql> call mysql.rds_show_configuration;
+------------------------+-------+------------------------------------------------------------------------------------------------------+
| name                   | value | description                                                                                          |
+------------------------+-------+------------------------------------------------------------------------------------------------------+
| binlog retention hours | NULL  | binlog retention hours specifies the duration in hours before binary logs are automatically deleted. |
+------------------------+-------+------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

デフォルト値は _NULL(バイナリログを保持しない)となっています。
これを24時間に変更します。

保持時間変更(NULL→24)
mysql> call mysql.rds_set_configuration('binlog retention hours', 24);
Query OK, 0 rows affected (0.06 sec)

変更できたらもう一度確認してみます。

保持時間確認
mysql> call mysql.rds_show_configuration;
+------------------------+-------+------------------------------------------------------------------------------------------------------+
| name                   | value | description                                                                                          |
+------------------------+-------+------------------------------------------------------------------------------------------------------+
| binlog retention hours | 24    | binlog retention hours specifies the duration in hours before binary logs are automatically deleted. |
+------------------------+-------+------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

24時間に変更されました。

デフォルト値に戻すにはNULLを与えればOKです。

保持時間変更(24→NULL)
mysql> call mysql.rds_set_configuration('binlog retention hours', NULL);
Query OK, 0 rows affected (0.00 sec)
保持時間確認
mysql> call mysql.rds_show_configuration;
+------------------------+-------+------------------------------------------------------------------------------------------------------+
| name                   | value | description                                                                                          |
+------------------------+-------+------------------------------------------------------------------------------------------------------+
| binlog retention hours | NULL  | binlog retention hours specifies the duration in hours before binary logs are automatically deleted. |
+------------------------+-------+------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.00 sec)

デフォルト値に変更されました。

注意事項

  • binlog retention hours の最大値はMySQLの場合は168時間(7日)、Amazon Aurora DBの場合は720時間(30日)です。
  • 利用可能なMySQLのバージョンは5.6および5.7です。(2017年4月25日現在)

おわりに

バイナリログの保持期間を設定した場合、バイナリログに必要以上の容量を使用されないよう、ストレージ使用状況をモニタリングするようにしましょう。

続きを読む

ECSのAMIで使われる/dev/xvdczについて【cloudpack大阪ブログ】

cloudpack大阪の佐々木です。
ECSでコンテナ用のボリュームを作ったらどこに保存されるか?という話です。

ECSで使うamazon-ecs-optimizedのAMIはデフォルトでは下記のディスク構成になっています。(2015.09.d 以降)

  • /dev/xvda 8G
  • /dev/xvdcz 22G

http://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/launch_container_instance.html

Amazon ECS に最適化された、2015.09.d 以降の AMI を使用している場合、インスタンスには 2 つのボリュームが設定されます。[Root] ボリュームはオペレーティングシステム用で、2 番目の Amazon EBS ボリューム (/dev/xvdcz にアタッチ) は Docker 用です。

永続化データを取り扱う場合、ボリュームを追加すると思います。/dev/xvdcz はDocker用ボリュームってことなので、当然そちらに作られるのかと思ったのですが、違うようです。

https://github.com/aws/amazon-ecs-agent/issues/312

/dev/xvdcz is dedicated to layer storage; since volumes are not layers, they’re not stored there.

実際にやってみます。
タスク定義はこんな感じです。

"volumes": [
  {
    "name": "volume-0",
    "host": {
        "sourcePath": "/ecs/mysql"
    }
  }
]

dfではRootボリュームの/dev/xvda1 だけが見えています。

$ df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
/dev/xvda1       7.8G  1.7G  6.1G   22% /
devtmpfs         1.9G   96K  1.9G    1% /dev
tmpfs            1.9G     0  1.9G    0% /dev/shm

コンテナにログインし、マウントしているボリュームに2Gのファイルを作成します。

$ docker exec -it 706e24a04caa /bin/bash
root@706e24a04caa:/# dd if=/dev/zero of=/var/lib/mysql/test.out bs=1024 count=2000000
root@706e24a04caa:/# ls -lh /var/lib/mysql/test.out
-rw-r--r-- 1 root root 2.0G Apr 25 04:47 /var/lib/mysql/test.out

ログアウトして、dfを実行してみます。

[ec2-user@ip-172-31-49-18 ~]$ df -h
ファイルシス   サイズ  使用  残り 使用% マウント位置
/dev/xvda1       7.8G  3.6G  4.2G   47% /
devtmpfs         1.9G   96K  1.9G    1% /dev
tmpfs            1.9G     0  1.9G    0% /dev/shm

/dev/xvda1 が増えています。

ではDocker用のボリューム /dev/xvdcz はどんな感じで使われているのでしょうか?
ここに詳しく書いてました。
http://enakai00.hatenablog.com/entry/20140420/1397981156

ホストOSからはこんな感じで見えています。

# lsblk
NAME                                                                                         MAJ:MIN   RM  SIZE RO TYPE MOUNTPOINT
xvda                                                                                         202:0      0    8G  0 disk
└─xvda1                                                                                      202:1      0    8G  0 part /
xvdcz                                                                                        202:26368  0   50G  0 disk
└─xvdcz1                                                                                     202:26369  0   50G  0 part
  ├─docker-docker--pool_tmeta                                                                253:0      0   52M  0 lvm
  │ └─docker-docker--pool                                                                    253:2      0 49.5G  0 lvm
  │   ├─docker-202:1-263192-5c6d48e2f8751428fe02afdadbd0cea776f6019d7ea6b84b3313a66d0c1f8ed7 253:3      0   10G  0 dm
  │   ├─docker-202:1-263192-eca78b256bf5721af414556f0ab42f4436e5ddd2dfb060da1f0ccf843cdbe11a 253:4      0   10G  0 dm
  │   ├─docker-202:1-263192-035491c621338eac035f0a3ee3894dc7c02c0f2989a33bce5e4628226edb1f10 253:5      0   10G  0 dm
  │   ├─docker-202:1-263192-d27d60081d632b3cc0e5b7c7213f40b4eec77d445d0c445abdf69efc83535d54 253:6      0   10G  0 dm
  │   └─docker-202:1-263192-60b1cb416a8ced69c0f6f5c71b842298427dfa406dd7ed6f5f44a56dc3d5f78f 253:7      0   10G  0 dm
  └─docker-docker--pool_tdata                                                                253:1      0 49.5G  0 lvm
    └─docker-docker--pool                                                                    253:2      0 49.5G  0 lvm
      ├─docker-202:1-263192-5c6d48e2f8751428fe02afdadbd0cea776f6019d7ea6b84b3313a66d0c1f8ed7 253:3      0   10G  0 dm
      ├─docker-202:1-263192-eca78b256bf5721af414556f0ab42f4436e5ddd2dfb060da1f0ccf843cdbe11a 253:4      0   10G  0 dm
      ├─docker-202:1-263192-035491c621338eac035f0a3ee3894dc7c02c0f2989a33bce5e4628226edb1f10 253:5      0   10G  0 dm
      ├─docker-202:1-263192-d27d60081d632b3cc0e5b7c7213f40b4eec77d445d0c445abdf69efc83535d54 253:6      0   10G  0 dm
      └─docker-202:1-263192-60b1cb416a8ced69c0f6f5c71b842298427dfa406dd7ed6f5f44a56dc3d5f78f 253:7      0   10G  0 dm

まとめ

ECSで永続化データ用のボリュームを使う場合は、Rootボリュームをあらかじめ大きくしておくか、別ボリュームをアタッチしてマウントしとく必要があるようです。

続きを読む

AWS の Ubuntu 16.04 で Etherpad as a service

AWS上でEtherpadを動かし、外からアクセスするようにできるまでのメモ書き。
DBはmysqlを使用。pluginはテーブルと画像挿入のをインストール。

前準備
AWS上のUbuntuパスワード変更
http://d.hatena.ne.jp/thata/20101116/1289890621

nodesインストール(次のに含まれているから不要?)
http://qiita.com/yuji_azama/items/45c0b413453534dba291

Ubuntu 16.04 に Etherpad をインストール
(コマンドを入力したら実行できる状態まで)
https://linuxconfig.org/install-etherpad-web-based-real-time-collaborative-editor-on-ubuntu-16-04-linux

起動時サービス
(サーバ起動時に自動でEtherpadが起動できるようにする)
http://pangency.hatenablog.com/entry/2015/05/08/111025

script の中身は

#! /bin/sh

case "$1" in
  start)
    /opt/etherpad/bin/run.sh
    ;;
esac

exit 0

とします。

ドメイン設定
https://ja.amimoto-ami.com/2013/11/29/elastic-ip-and-route-53/

nginxをリバースプロキシサーバとして使う。
(nodejs は80番ポートで起動できない?)
以下、Ethepadのデフォルトポートは9001 だが、settings.jsでポートを8080に変えた時の例。

upstream web-nodejs {
  server localhost:8080;
}
server {
  listen       80;
  server_name  etherpad.dslabgroup.com;

  location / {
    proxy_pass http://web-nodejs/;
  }
}

外からは80番ポートでアクセスするので、AWSのセキュリティの設定は、80番ポートに外からアクセスできるようにしていればいい。

nginx enableする (サーバ起動時にnginx起動)
https://www.digitalocean.com/community/tutorials/how-to-configure-a-linux-service-to-start-automatically-after-a-crash-or-reboot-part-1-practical-examples

plugins
https://static.etherpad.org/plugins.html # ここにPlugin一覧がある
https://help.ubuntu.com/community/Etherpad-liteInstallation#Plugins

cd /opt/etherpad-lite
npm install ep_tables2
npm install ep_copy_paste_images
# AWSでインスタンス再起動する
# (上記で自動でrun.shをするようにした場合の、サービスだけの再起動方法不明。。)

mysql 設定
デフォルトではjs製のDB DirtyDBを使うようになっている。データ毎(?)の容量の上限が1M?らしいので、mysqlに変える。
下記設定だけを行うと、create tableなどはetherpad 起動時に自動で行われる。
https://github.com/ether/etherpad-lite/wiki/How-to-use-Etherpad-Lite-with-MySQL
https://help.ubuntu.com/community/Etherpad-liteInstallation

続きを読む

RDSのスロークエリログをmysqldumpslowに突っ込む

JSON形式で取得されるRDS(MySQL)のスロークエリログをcatして絶望したときに、心を落ち着けてmysqldumpslowに突っ込めるようにします。

用意するもの

  • awscli
  • jq
  • mysqldumpslow 1

スロークエリログのダウンロード

スロークエリログのリストを取得

$ aws rds describe-db-log-files \
  --db-instance-identifier hoge-instance \
  | jq '.DescribeDBLogFiles[].LogFileName' -r  \
  | grep slow
slowquery/mysql-slowquery.log
slowquery/mysql-slowquery.log.0
...

スロークエリログのダウンロード

describe-db-log-files取得されたファイル名には/が含まれていて邪魔なので、ファイルに保存するときに取っています。

for log in $(cat slowlog-list.txt); do \
  aws rds download-db-log-file-portion \
  --db-instance-identifier hoge-instance \
  --log-file-name $log \
  > $(echo $log | cut -d / -f 2); done

スロークエリログの読み込み

取得されたスロークエリログの中身はJSONデータの奥に格納されたテキスト文字列なので、jqで抽出してからmysqldumpslowに食わせます。

$ cat mysql-slowquery.log \
  | jq -rs .[].LogFileData \
  | mysqldumpslow -
# 遅い順にソートされたクエリたち...

  1. Ubuntuではmysql-clientをインストールするとついてきます。 

続きを読む

RDSをAnsibleで管理する

はじめに

AnsibleにはAWSのリソースを操作できるモジュールが豊富に用意されています。

今回は、RDSをAnsibleで管理してみます。
RDSインスタンスを作成するためには、

  • サブネットグループ
  • パラメータグループ

が必要になるので、一気通貫で作成できるようにします。

やること

  • サブネットグループ作成
  • パラメータグループ作成
  • RDSインスタンス作成

ポイント

サブネットグループを作成するためにサブネットIDが、RDSインスタンスを作成するためにセキュリティグループIDがそれぞれ必要となりますが、IDをAnsibleのYAMLに書きたくないので、それぞれ名前からIDを取得する実装とします。

前提

  • AWS関連のモジュール実行にはbotoが必要です。
  • credential情報は環境変数かaws configureでセットしてある必要があります。
  • ec2_group_factsが必要です。

下記リソースを前提に進めます。

  • VPC

    • AnsibleVPC
  • サブネット
    • private-a
    • private-c
  • セキュリティグループ
    • db_server

sample

以下のようなRDSインスタンスを作成します。

  • サブネットグループ

    • private

      • private-a(AZ-a)
      • private-c(AZ-c)
  • パラメータグループ
    • mysql57-sample
  • RDSインスタンス
    • sample-db

      • セキュリティグループ

        • db_server
      • サブネットグループ
        • private
      • パラメータグループ
        • mysql57-sample

ディレクトリ構成

ディレクトリ構成
site.yml
roles/
|--rds/
|  |--tasks/
|  |  |--main.yml
hosts/aws    #inventory
host_vars/
|--localhost.yml

inventory

AWSリソース関連モジュールはすべてlocalhostで実行するので、下記のようなインベントリファイルを用意します。

hosts/aws
[aws]
localhost

vars

こんな感じに変数を定義します。今回はhost_varsで定義しました。
RDSインスタンスのパスワードをそのまま記載していますが、実際はansible-vaultで暗号化したファイルなどに記載してください。

host_vars/localhost.yml
---
my_vars:
  aws:
    common:
      region: ap-northeast-1
    vpc:
      name: AnsibleVPC    # ターゲットのVPC名
    rds:
      subnet_group:
        - name: private
          description: 'private subnet group'
          subnets:
            - private-a #サブネット名
            - private-c #サブネット名
      param_group:
        - name: mysql57-sample
          description: 'MySql5.7 sample'
          engine: mysql5.7
          params:
            character_set_database: utf8
            character_set_server: utf8
      instance:
        - name: sample-db
          db_engine: MySQL
          engine_version: 5.7.16
          multi_zone: no
          zone: a
          size: 5
          instance_type: db.t2.micro
          parameter_group: mysql57-sample
          subnet_group: private
          security_group:
            - db-server #セキュリティグループ名
          username: mysql_admin
          password: mysql_admin
          tags:
            Role: sample-db

Role

まずVPCを特定するためにidが必要ですが、こちらと同様、VPC名でidを取得します。

後続タスクで必要となる、サブネットIDとセキュリティグループIDを取得し、それぞれ名前からIDを参照するためにディクショナリを生成します。

roles/vpc/tasks/main.yml
---
- name: vpc_id取得
  ec2_vpc_net_facts:
    region: "{{ my_vars.aws.common.region }}"
    filters:
      "tag:Name": "{{ my_vars.aws.vpc.name }}"
  register: vpc_net_fact

- name: subnet id取得
  ec2_vpc_subnet_facts:
    region: "{{ my_vars.aws.common.region }}"
    filters:
      vpc_id: "{{ vpc_net_fact.vpcs[0].id }}"
      "tag:Name": "{{ item.1 }}"
  with_subelements:
    - "{{ my_vars.aws.rds.subnet_group }}"
    - subnets
  register: subnet_fact
  when: my_vars.aws.rds.subnet_group is defined

- name: subnet dict作成
  set_fact:
    subnets_dict: >-
      {%- set dict = {} -%}
      {%- set inc = 0 -%}
      {%- set subnet_group_cnt = my_vars.aws.rds.subnet_group|length -%}
      {%- for i in range(subnet_group_cnt) -%}
      {%-   set list = [] -%}
      {%-   for j in range(my_vars.aws.rds.subnet_group[i].subnets|length) -%}
      {%-     set _ = list.append(subnet_fact.results[inc+j].subnets[0].id) -%}
      {%-   endfor -%}
      {%-   set _ = dict.update({my_vars.aws.rds.subnet_group[i].name: list}) -%}
      {%-   set inc = inc + subnet_group_cnt -%}
      {%- endfor -%}
      {{ dict }}
  when: my_vars.aws.rds.subnet_group is defined

- name: securitygroup id取得
  ec2_group_facts:
    region: "{{ my_vars.aws.common.region }}"
    filters:
      vpc_id: "{{ vpc_net_fact.vpcs[0].id }}"
      group_name: "{{ item.1 }}"
  with_subelements:
    - "{{ my_vars.aws.rds.instance }}"
    - security_group
  register: sg_fact
  when: my_vars.aws.rds.instance is defined

- name: securitygroup dict作成
  set_fact:
    sg_dict: >-
      {%- set dict = {} -%}
      {%- for i in range(sg_fact.results|length) -%}
      {%-   set _ = dict.update({sg_fact.results[i].security_groups[0].group_name: sg_fact.results[i].security_groups[0].group_id}) -%}
      {%- endfor -%}
      {{ dict }}
  when: my_vars.aws.rds.instance is defined

- name: RDS subnet-group作成
  rds_subnet_group:
    state: present
    name: "{{ item.name }}"
    description: "{{ item.description }}"
    region: "{{ my_vars.aws.common.region }}"
    subnets: >-
      {%- set subnetname = item.name -%}
      {%- set list = subnets_dict[item.name] -%}
      {{ list }}
  with_items: "{{ my_vars.aws.rds.subnet_group }}"
  register: rds_subnet_group

- debug: var=rds_subnet_group

- name: RDS パラメータグループ作成
  rds_param_group:
    state: present
    name: "{{ item.name }}"
    description: "{{ item.description }}"
    region: "{{ my_vars.aws.common.region }}"
    engine: "{{ item.engine }}"
    params: "{{ item.params }}"
  with_items: "{{ my_vars.aws.rds.param_group }}"
  register: rds_param_group
  when: my_vars.aws.rds.param_group is defined

- debug: var=rds_param_group

- name: RDS インスタンス作成
  rds:
    command: create
    instance_name: "{{ item.name }}"
    db_engine: "{{ item.db_engine }}"
    engine_version: "{{ item.engine_version }}"
    region: "{{ my_vars.aws.common.region }}"
    multi_zone: "{{ item.multi_zone }}"
    zone: "{{ my_vars.aws.common.region }}{{ item.zone }}"
    size: "{{ item.size }}"
    instance_type: "{{ item.instance_type }}"
    parameter_group: "{{ item.parameter_group }}"
    subnet: "{{ item.subnet_group }}"
    vpc_security_groups: >-
      {%- set list = [] -%}
      {%- for i in range(item.security_group|length) -%}
      {%-   set _ = list.append(sg_dict[item.security_group[i]]) -%}
      {%- endfor -%}
      {{ list }}
    username: "{{ item.username }}"
    password: "{{ item.password }}"
  with_items: "{{ my_vars.aws.rds.instance }}"
  register: rds_instance
  when: my_vars.aws.rds.instance is defined

- debug: var=rds_instance

site.yml

site.yml
---
- name: rds
  hosts: localhost
  connection: local
  roles:
    - role: rds

実行

Command
$ ansible-playbook -i hosts/aws -l localhost site.yml

注意

Ansibleのrdsモジュールにはなぜかストレージタイプを指定するためのオプションがなく、作成されたインスタンスのストレージはマグネティックになってしまいますので適宜変更してください。

まとめ

RDSは、関連リソースを合わせて作成する必要がありますが、Ansibleで自動化できると楽です。
また、今回のplaybookはインスタンスの変更には対応していません。

参考になれば幸いです。

参考

続きを読む

AWS上でインスタンスを構築してwordpressを起動するまで

About

wordpressをAWS上で立ち上げてみました。

利用技術

  • Terraform
  • Itamae

ソースコードはこちらです https://github.com/tjinjin/wordpress-sandbox

Terraform

main.tf
data "aws_ami" "centos" {
  most_recent = true

  filter {
    name   = "product-code"
    values = ["aw0evgkw8e5c1q413zgy5pjce"]
  }
}

resource "aws_instance" "wp" {
  ami                    = "${data.aws_ami.centos.id}"
  instance_type          = "t2.micro"
  key_name               = "tjinjin-terraform"
  vpc_security_group_ids = ["${aws_security_group.wp.id}"]

  root_block_device {
    delete_on_termination = "true"
  }

  tags {
    Name = "wordpress"
  }
}

resource "aws_security_group" "wp" {
  name        = "wordpress"
  description = "wordpress sg"

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["${var.my_ipaddr}"]
  }

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["${var.my_ipaddr}"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags {
    Name    = "wordpress"
    Created = "terraform"
  }
}

output "wordpress_ip" {
  value = "${aws_instance.wp.public_ip}"
}

AMIはcentosの公式のものを使用しています。product-codeはこちらのページから判断しました。 https://wiki.centos.org/Cloud/AWS

Itamae

一部プラグインを使いつつ、他は自分で書きました。もともとchefを使っている人間だったのでhttp_request というresource名を知らなかったため、苦労しましたw

$ itamae ssh --host aws-wordpress roles/wp.rb 

これをすれば適用できます。

残作業

ここまでくればあとはmysqlの設定とwordpress上での設定だけです。

$ cat /var/log/mysqld.log | grep generate
$ mysqladmin -u root password -p
$ mysql -u root -p

mysql> create database wordpress;
mysql> grant all privileges on wordpress.* to wordpress@localhost identified by '<password>';

あとは画面で設定をすれば一応起動するところまで到達できます。

続きを読む

AWSのセキュリティグループをAnsibleで管理する

はじめに

AnsibleにはAWSのリソースを操作できるモジュールが豊富に用意されています。

今回は、複雑になりがちなセキュリティグループをAnsibleで管理してみます。

やること

  • セキュリティグループ作成
  • ルール作成(インバウンドのみ)

ポイント

セキュリティグループは、通信元、宛先をIPアドレスではなくセキュリティグループを指定することもできるため、Ansibleで作成する際に矛盾が生じることもあります。

例えば、
A:Bからの通信を許可
B:Aからの通信を許可
のようなケースの場合、Aの作成時にはBが存在しないとエラーになりそうですが、実際はルールが空の状態でBも作成されます。

Ansibleで構成する場合、YAMLであるべき構成を定義していくことになります。
with_dictでループさせて作成する場合、ループの順序が考慮されないため、Aの作成時にBが存在しない等でタスクがfailしてしまうことがありますが、上記の通り、空のBが作成されていますので、もう一度タスクを実行すると成功します。

このあたりの挙動に気をつけて実装していきます。

前提

AWS関連のモジュール実行にはbotoが必要です。
credential情報は環境変数かaws configureでセットしてある必要があります。

sample

以下のようなセキュリティグループを作成します。

  • common

    • xxx.xxx.xxx.xxxからSSH許可
    • commonからのICMPを全て許可
  • web_server
    • AnyからHTTP許可
  • db_server
    • web_serverからMySQL許可

ディレクトリ構成

ディレクトリ構成
site.yml
roles/
|--security_group/
|  |--tasks/
|  |  |--main.yml
hosts/aws    #inventory
host_vars/
|--localhost.yml

inventory

AWSリソース関連モジュールはすべてlocalhostで実行するので、下記のようなインベントリファイルを用意します。

hosts/aws
[aws]
localhost

vars

こんな感じに変数を定義します。今回はhost_varsで定義しました。

host_vars/localhost.yml
---
my_vars:
  aws:
    common:
      region: ap-northeast-1
    vpc:
      name: AnsibleVPC    # ターゲットのVPC名
    security_group:
      common:
        group_name: common
        description: common rules
        rules:
          - proto: tcp
            from_port: 22
            to_port: 22
            cidr_ip: xxx.xxx.xxx.xxx/32
          - proto: icmp
            from_port: -1
            to_port: -1
            group_name: common
      web_server:
        group_name: web_server
        description: web_server rules
        rules:
          - proto: tcp
            from_port: 80
            to_port: 80
            cidr_ip: 0.0.0.0/0
      db_server:
        group_name: db_server
        description: db_server rules
        rules:
          - proto: tcp
            from_port: 3306
            to_port: 3306
            group_name: web_server

Role

まずVPCを特定するためにidが必要ですが、こちらと同様、VPC名でidを取得します。

今回はリストではなくディクショナリとしてセキュリティグループを定義しましたので、with_dictでループさせます。

ルールの定義はリストで複数指定できますので、そのようにvarsに定義しました。
このままカンマで繋げてtaskで使用します。

ただし、1つしか定義されていない場合、不要なカンマがついてしまうので、要素数で処理を分岐させています。

アウトバウンドは、多くの場合デフォルト(全許可)で使用すると思うので、vars未定義の場合デフォルト値が採用されるようにしています。

セキュリティグループの構成上、一度目のtaskがfailした場合、リトライするようにしました。

roles/vpc/tasks/main.yml
---
- name: vpc_id取得
  ec2_vpc_net_facts:
    region: "{{ my_vars.aws.common.region }}"
    filters:
      "tag:Name": "{{ my_vars.aws.vpc.name }}"
  register: vpc_net_fact

- debug: var=vpc_net_fact

- name: セキュリティグループ作成
  ec2_group:
    name: "{{ item.value.group_name }}"
    description: "{{ item.value.description }}"
    region: "{{ my_vars.aws.common.region }}"
    vpc_id: "{{ vpc_net_fact.vpcs[0].id }}"
    rules: >-
      {%- if item.value.rules|length == 1 -%}
        {{ item.value.rules }}
      {%- else -%}
        {{ item.value.rules | join(',') }}
      {%- endif -%}
    rules_egress: >-
      {%- if item.value.rules_egress is undefined -%}
        {%- set egress_item = {"proto":"all", "from_port":"0", "to_port":"65535", "cidr_ip":"0.0.0.0/0"} -%}
        {%- set egress = [egress_item] -%}
        {{ egress }}
      {%- elif item.value.rules_egress|length == 1 -%}
        {{ item.value.rules_egress }}
      {%- else -%}
        {{ item.value.rules_egress | join(',') }}
      {%- endif -%}
  with_dict: "{{ my_vars.aws.security_group }}"
  when: my_vars.aws.security_group is defined
  ignore_errors: yes
  register: sg

- debug: var=sg

- name: セキュリティグループ作成リトライ
  ec2_group:
    name: "{{ item.value.group_name }}"
    description: "{{ item.value.description }}"
    region: "{{ my_vars.aws.common.region }}"
    vpc_id: "{{ vpc_net_fact.vpcs[0].id }}"
    rules: >-
      {%- if item.value.rules|length == 1 -%}
        {{ item.value.rules }}
      {%- else -%}
        {{ item.value.rules | join(',') }}
      {%- endif -%}
    rules_egress: >-
      {%- if item.value.rules_egress is undefined -%}
        {%- set egress_item = {"proto":"all", "from_port":"0", "to_port":"65535", "cidr_ip":"0.0.0.0/0"} -%}
        {%- set egress = [egress_item] -%}
        {{ egress }}
      {%- elif item.value.rules_egress|length == 1 -%}
        {{ item.value.rules_egress }}
      {%- else -%}
        {{ item.value.rules_egress | join(',') }}
      {%- endif -%}
  with_dict: "{{ my_vars.aws.security_group }}"
  when: my_vars.aws.security_group is defined and sg | failed
  register: sg_retry

- debug: var=sg_retry

site.yml

site.yml
---
- name: security_group
  hosts: localhost
  connection: local
  roles:
    - role: security_group

実行

Command
$ ansible-playbook -i hosts/aws -l localhost site.yml

まとめ

同じタスクを2つ定義するのがカッコ悪いですが、なんとかセキュリティグループを管理できるようになりました。

セキュリティグループはマネコンからだとどういう構成になっているか把握しづらいですが、AnsibleのYAMLで管理すると見通しがよくなるのではないでしょうか。

参考

続きを読む

ド素人がAWSでRuby on Railsを導入したメモ

はじめに

いい加減見るだけでなくなにかしら自分用にでもいいからアウトプットしてみようと思ったので書いてみました。
ほぼグラフィックデザイナーとしてしかやってこなかった自分が軽い気持ちで手を出しているので、そもそも理解できてない部分が多い上に無駄な工程、逆になぜそこを書かないんだ的なところもどっさりあるかと思います。
(一応、自分のWindows10PCに仮想環境あり、PHPとかjQueryとかMysqlとかほんの触り程度に知っているくらいですが超初級レベルです。)

参考にした記事とやったこと

導入できました!

その後、rubyは入ってるのに
$ ruby -v
が効かなくてrehashも結局試したけどだめだったので一度接続しなおしたらバージョンが表示されました。導入できたようです。ヤッタネ!

今後の課題とか所感

Railsをインストールするディレクトリ
何も考えずec2-user直下にインストールしたがこれで果たして良かったのか・・・
まだ何もいじっていないのでわからんですがちゃんと調べてから導入したほうがいいなと後から感じました。

開発環境に慣れる
これから仕事で使うことになるので否が応でも慣れるしかない・・・まずは何ができて何ができないのかを理解するところからですかね。

まとめるのって難しい!
今回こうして書いてみたら思っていた以上に書き起こすのが難しかったのと、Markdown方式できれいな記事に仕上げるのもなかなか至難の技だということがわかりました。
あとはいろんな人の記事をもっと読んで、また自分が書こうと思ったときに書いてぼちぼち慣れていければなと思います。

続きを読む