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日現在)

おわりに

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

続きを読む

AMLのシステム構成

機械学習に関する基本的な内容をまとめてみたものです。機械学習に関する、Web上にすでにある解説コンテンツをまとめたサイトの抜粋です。
AMLのシステム構成

完全マネージドの機械学習サービス

Amazon Machine Learning(AML)は、機械学習を行うためのインフラストラクチャやワークフローなどが不要の完全マネージド型機械学習サービスです。

そのため、
・ハードウェアのプロビジョニング
・コンピュータの負荷の分散やスケーリング
・リソース間の依存関係の管理
などに配慮する必要はありません。

ユーザーは使用するデータを用意するだけで、機械学習を行う事が出来ます。

システム構成

Amazon Machine Learningを使った機械学習システムは、
・データソースなどの入出力をするために使うS3、RedShift、RDS
・学習モデルの構築を構築して予測を行うAmazon Machine Learning
から構成されます。
シンプルなシステム構成となっているので、手軽に利用する事が出来ます。

複数の予測を一度に行うバッチ予測をするために、AWS APIを使ったクライアントアプリケーションを利用する事も可能です。

可用性の高いシステム構成

・モデルのトレーニング、評価、予測などは、Amazonのデータセンターで実行
・AWSのリージョン別に、サービススタックレプリケーションが複数の施設に分散
・メンテナンスなどによるダウンタイムはない

などの工夫がされています。

そのため、サーバーの障害やアベイラビリティーゾーンの機能停止に対しても、万全の対策が施されています。

続きを読む

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はインスタンスの変更には対応していません。

参考になれば幸いです。

参考

続きを読む

Shoryuken を導入しようとして諦めた話

Rails で非同期処理といえば、 Sidekiq, Resque, DelayedJob あたりが有名かと思います。
DelayedJob は RDS をジョブキューとして利用できる1ため、インフラの準備が不要で比較的ライトに導入できますが、数万件のジョブを登録しようと思うと RDS にかなりの負荷がかかりますし、Insert もそれほど早くないため、ジョブキューとしてはあまり適しているとは言えません。
Redis などのインメモリーデータベースを用いれば、RDS に負荷をかけず、Insert の高速化も見込めますが、万が一 Redis がクラッシュした際には登録されていたキューは全て無くなりますし、そういった障害に対応するためには幾らかの運用コストが発生します。

ところで、 AWS には SQS というキューイングサービスが提供されています。
安価で堅牢な造りになっており、運用コストも低そうだったのでジョブキューとしては最適なように感じましたが、SQS をジョブキューとして利用している非同期処理と言えば Shoryuken くらい2です。

今回、DelayedJob からの移行先として、Shoryuken が使えないか検討したのですが、いくつかの理由により結局諦めた、という話を書いてみます。

環境

  • Ruby 2.4.1
  • Ruby on Rails 5.0.2
  • Shoryuken 3.0.6

Shoryuken とは

SQS を使った Job queue worker。ActiveJob にも対応。
Sidekiq を意識して作られており、スレッドベース。

しょーりゅーけん!
GitHub Shoryuken

なぜ Shoryuken が良いと思ったのか?

  • SQS は安い

    • 100万 requests / month まで無料
    • 1億 requests / month で $39.60 (およそ 4,300円)
  • SQS だとインスタンスタイプの変更などの運用が発生しない
  • SQS 側でリトライをサポートしている
    • 一定時間内に処理が正常に完了しなかった場合、再度 Queue に入る
    • 一定回数失敗した場合、Dead letter queue に移動される
    • Redis の場合、 Sidekiq の worker プロセスが吹っ飛んだ場合にジョブが失われる恐れがある
      • Redis 本体が死んでもデータは吹っ飛ぶ
      • Sidekiq Pro ($950 / year) ならリカバリ可能らしい
  • 昇龍拳だから

SQS の仕組み

キューの種類

標準キュー

Amazon SQS のデフォルトのキューの種類は標準です。標準キューでは、1 秒あたりのトランザクション数はほぼ無制限です。標準キューではメッセージが少なくとも 1 回配信されることが保証されます。ただし (高いスループットを可能にする、徹底して分散化されたアーキテクチャであるために) ときとして、 メッセージのコピーが乱れた順序で複数配信されることがあります 。標準キューでは、メッセージが通常は送信されたのと同じ順序で配信されるようにする、ベストエフォート型の順序付けが行われます。
SQS よくある質問 – 標準キューと FIFO キューの違いは何ですか?

標準キュー

乱れた順序で複数配信されることがあります。

つまり、稀に複数の worker が同じ Job を実行してまう可能性があるということ。
また、Queue と言ってるけど、順番は保証しない(ベストエフォートなので一応は考慮してる)ということ。

複数回同じジョブが実行されてしまうのは困るけど、ジョブの処理の中で RDS などに処理の進行状態を保存して、複数回実行されないような実装をすれば回避可能。というか、 そういう実装が必須 だと思います。

一番の強みはトランザクション数がほぼ無制限なので、複数の worker から同時にアクセスしまくっても全然大丈夫です。

FIFO キュー

FIFO キューは、標準キューを補完するものです。このキュータイプの最も重要な特徴は、FIFO (先入れ先出し方式) 配信と 1 回限りの処理です。メッセージが送受信される順序は厳密に保たれます。メッセージは 1 回配信されると、消費者がそれを処理して削除するまでは使用できるままとなり、キューで重複が起きることはありません。FIFO キューはメッセージグループもサポートしているため、1 つのキュー内に複数の順序付けられたメッセージグループが可能です。FIFO キューは、1 秒あたり 300 トランザクション (TPS) に制限されていますが、標準キューの機能をすべて備えます。
SQS よくある質問 – 標準キューと FIFO キューの違いは何ですか?

FIFOキュー

キューの順序と1回限りであることを保証する代わりにトランザクション数が 300 TPS に制限されたバージョン。 EC サイトの決済処理とかで使う、みたいな例が書かれていました。

1 秒あたり 300 トランザクション = 1 分あたり 18000 トランザクション。
現在開発中のシステムでは要件として5分で 10000 件の処理を目標にしているジョブだったので、これでは物足りない感じ。

可視性タイムアウト (Visibility Timeout)

コンシューマーがキューからメッセージを受信して処理しても、そのメッセージはキューに残ったままです。Amazon SQS では、メッセージは自動的に削除されません。これは分散システムであるため、コンポーネントがそのメッセージを実際に受信するという保証がないからです (接続が切断されたり、コンポーネントでメッセージの受信に失敗する可能性があります)。そのため、コンシューマーはメッセージを受信して処理した後、キューからメッセージを削除する必要があります。
SQS 開発者ガイド – 可視性タイムアウト

可視性タイムアウト

引用の通りですが、SQS には可視性タイムアウトという設定値があって、Shoryuken の worker が SQS からメッセージを受け取ると、SQS 側はメッセージを一定時間、論理削除します。
worker はジョブが完了すると SQS 側にメッセージの削除要求を投げて、論理削除されていたメッセージを物理削除します。
もし、 worker 側で処理に失敗、または可視性タイムアウトの設定時間を経過しても処理が終わらなかった場合、SQS 側で論理削除を解除して、次のリクエストでメッセージを再配信します。
この仕組みがあるため、 Shoryuken 側でリトライを設定していなくとも、失敗した処理は再度実行されるようになります。
万が一 worker のプロセスが死んでしまっても、 SQS 上でメッセージはちゃんと生きている訳です。

worker の処理時間が長すぎて可視性タイムアウトの時間を過ぎてしまうのは問題なので、 2 分かかる処理なら可視性タイムアウトを4分に設定する、などがセオリーのようです。

なお、Shoryuken には auto_visibility_timeout というオプションがあって、これを true にしておくと、可視性タイムアウトに達する 5 秒前に可視性タイムアウトの延長をしてくれるようです。

遅延キュー

遅延キューを使用すると、キューにある新しいメッセージの配信を指定の秒数延期できます。遅延キューを作成した場合、そのキューに送信したすべてのメッセージが遅延期間の間コンシューマーに表示されなくなります。DelaySeconds 属性を 0 ~ 900 (15 分) の任意の値に設定することで、CreateQueue アクションを使用して遅延キューを作成できます。
(中略)
遅延キューは、メッセージを一定の時間コンシューマーが使用できなくするため、可視性タイムアウトと似ています。遅延キューと可視性タイムアウトの違いは、遅延キューの場合、メッセージが最初にキューに追加されたときに非表示になるのに対して、可視性タイムアウトの場合、メッセージがキューから取得された後のみ非表示になるという点です。
SQS 開発者ガイド – 遅延キュー

遅延キュー

ActiveJob ではオプションとして #set(wait: 1.minute) などが使用できますが、 Shoryuken でこの実現に用いられるのが遅延キューです。SQS 側で遅延時間に 0 ~ 15 分という制約があるので、 Shoryuken では 15 分以上の遅延実行はサポートされていません。

デッドレターキュー (Dead letter queue)

Amazon SQS では、デッドレターキューがサポートされます。デッドレターキューは、正常に処理できないメッセージの送信先として他の (送信元) キューが使用できるキューです。これらのメッセージは、処理が成功しなかった理由を判断するためにデッドレターキューに分離できます。
SQS 開発者ガイド – デッドレターキュー

これも引用の通りですが、 SQS 側で設定しておけば一定回数失敗したメッセージが別のキューに自動的に移動されるようになります。失敗回数は 1~1000 で設定でき、これで失敗時のリトライ回数の上限が設けられるようです。

おそらく前述の可視性タイムアウトで失敗した回数だと思うのですが、 SQS のドキュメントでは記述が見つからなかったので未確認です。

結局使うのは諦めました

SQS の堅牢さには心惹かれるものがあったので、ぜひ利用したかったのですが、いくつかの理由により見送ることにしました。

Shoryuken での設定値の共有がイマイチ

config/shoryuken.yml に AWS の設定などを記述するのですが、どうやらこれは worker が参照するための設定らしく、Rails アプリ側ではこの値を読み込んではくれないようです。なのでジョブの登録時に AWS の設定が反映されておらず、エラーになりました。

Shoryuken の wiki によると、AWS クライアントのインスタンス作成処理を Shoryuken に任せずに自分で作るなどすれば良いようですが、せっかく config/shoryuken.yml が存在しているのだから、 Shoryuke 側で反映させて欲しいな、と思いました。
なお、参考先のサイト では、Rails の config/initializersShoryuken::EnvironmentLoader.load(config_file: "config/shoryuken.yml") を実行して Rails 側に設定を読み込ませる方法が紹介されていましたが、僕の環境ではキュー名の Prefix が二重に付いてしまったりして、うまく動作してくれませんでした。

また、 Rails.env によって設定を変える、といったことが Shoryuke 側ではサポートされていないので、 staging 環境では少し worker の thread 数減らそう、とかそういった設定がいちいち面倒なのも残念でした。

100万トランザクションは簡単に突破しそう

100万トランザクションまで無料、という SQS ですが、Queue が空でメッセージが配信されない場合もトランザクションとして計上されているようです(料金に計上されるかは未確認なので、もし違ったら教えてください。)
Shoryuken は 1 プロセスで標準 25 スレッド並列に処理が走るので、ローカルで1時間弱動かしただけでも 40000トランザクションを超えました。

ただし、Shoryuken 側のオプションで delay という項目があり、メッセージが空の場合は delay で指定した期間はリクエストをストップするので、この値を調整すればなんとかなりそうです。
しかし、あまり長い秒数 delay してしまうと Job の即時実行ができないので、要件に合わせた慎重な検討が必要です。

個人で開発された gem である

一番大きな点はここです。
現在も頻繁に開発されているのですが、割と作者本人が自分で PR 出して自分で merge という流れが多いようです。
あくまで作者が個人的に開発している gem であり、商用利用を考慮した慎重な開発はされていないような印象を持ちました。

ただ、ローカルで動かしてみただけですが、非常に軽快に動作しており、worker としての核はよくできている gem だと感じました。SQS についても Redis より堅牢なように感じたので、かなり本腰を入れて検討したのですが、ジョブで実行している処理は開発中のアプリの核になる機能なので、ちょっとここはリスクと考え、慎重になりました。

最後に

ジョブのキューに SQS を利用する、という観点は非常に良いように思いますし、他に SQS に対応した Job queue worker は存在しなさそうなので、個人的には Shoryuken を応援しています。
今回は商用利用のアプリ用だったので見送りましたが、個人で作成するアプリではぜひ使ってみたいと思います。

参考


  1. DelayedJob は RDS 以外のバックエンドも サポートしています 

  2. 他にもDelayedJobSQSがありますが、4年ほど開発が止まっているようです。  

続きを読む

CloudFormationの特徴とはじめ方

はじめに

本記事はCloudformationを始めようとしている人向けです。
細かい技術的な話よりは、特徴だったり始め方の手順だったりが書かれています。
公式ドキュメントを舐めるのが面倒な人はちょうどいいです。

概要

インフラ環境をテンプレート化し、何度実行しても同じ環境を簡単に構築することができる。
AWSのアイコンをペタペタ並べていくことでテンプレートを新規作成する「Design Template」機能や、既存のインフラ環境を自動でテンプレート化してくれる「CloudFormer」という機能がある。
また、コードでテンプレートを作成することも可能であり、デフォルトの言語はJSONとなっている。

メリット

大きな特徴として、以下が挙げられる。

  1. インフラがコードで管理できること
  2. 作成・削除・更新が容易であること

何がもたらされるのか

  • コードで管理できるということは、バージョン管理が容易になる。
    「インフラの成長過程がわかる」とよく言われる所以
  • オペレーションミスなし、作業者の技術力に依存しない環境構築が可能になる
  • 運用中、値変更があってもリスクなしでアップデートできる
  • 開発環境など限定的にしか使用しないインフラのランニングコストを削減できる
  • 既存のインフラ環境をすぐに可視化できる(構成図を作成・更新する工数削減)

CloudFormationの金額

無料で使用できる。立ち上がったリソースに対して課金が発生する。

機能

CloudFormation Designer

おなじみのAWSアイコンを配置し、繋いでいくことでテンプレートが作成できる。
作成したDesignは保存、ダウンロード、ローカルから開く、などができる。
アイコンは単純なAWSサービスだけではなく、ルートテーブルやVPCEndpointなど、普段は見ないアイコンも存在する。
理由は、この機能は構成図を作成するためのものではなく「テンプレートを作成する」機能のため、詳細なポリシーまで指定する必要がある。

CloudFormer

既存で構築されているインフラ環境をテンプレートに落とし込める機能。
自動でCloudFormation用のインスタンスを作成し、テンプレート作成準備を数分で構築する。
すべてのCloudFormationのリソースをサポートしている。
注意点としては、新規インスタンスを一台起動させて実行するため、課金が発生する。

詳しい特徴は以下の通り

  • どのリソースをテンプレートに含めるか制御可能
  • リソースを選択すると、従属するリソースも自動選択(変更可能)
  • EC2インスタンスを選択すると、そのインスタンスが起動された元のAMIが指定される

CloudFormerで生成されたテンプレートを手修正し、最終的なテンプレートとして利用することを推奨している

開始手順

CloudFormerスタックを作成する

  1. Cloudformation画面にて「Create New Stack」をクリック
  2. 「Select a sample template」のプルダウンから「Cloudformer」を選択
  3. 「Next」で次ページ
  4. 必要な情報を入力する
  5. Stack Name CloudFormerのスタック名
  6. Password CloudFormerにてテンプレートを作成するページへログインするためのパスワード
  7. Username CloudFormerにてテンプレートを作成するページへログインするためのユーザ名
  8. VPCSelection CloudFormer用インスタンスが立ち上がるVPC。特に変更はしない

  9. 「Next」で次ページ

  10. 必要な情報を入力する(特に設定しなくても進められる)

  11. TagとKeyの設定。リソースにタグを適用できるため、識別や分類したい場合は設定する

  12. Permissionsの権限がないとCloudformationは実行できないため、必ず実行権を持ったロールアカウントにする

  13. 「Next」で次ページ

  14. サマリを確認後、「Create」をクリック

  15. スタック作成が実行されるため、起動完了の「CREATE_COMPLETE」となるまで待機

Cloudformationテンプレートを作成する

  1. CloudFormerのスタックを選択し、「Outputs」タブを選択
  2. 「Value」に表示されるURLを踏み、テンプレート作成画面へ飛ぶ
  3. 「アクセス保護されてないページ」の警告を通過する
  4. テンプレート化を実施するリージョンを選択し「Continue」をクリック
  5. テンプレートに含めたいリソースの選択が続く。含める含めないを考慮しながら「Continue」をクリックしていく
  6. サマリを確認後、「Done」をクリック
  7. テンプレートが作成されているので、S3のどのバケットに保存するか選択する
  8. 終了

Cloudformerスタックを削除する

  1. テンプレート化が確認されたらスタックを削除(インスタンス分が課金されている)
  2. 終了

JSONテンプレート

テンプレートはデフォルトでJSON形式で記述される。
いくつかのセクションに分かれており、そのうち「Resources」セクションのみ必須で記述する必要がある。

セクションは以下の通り

  • Format Version(任意)

    CloudFormationのテンプレートバージョンを指定する
    
  • Description(オプション)
    テンプレートを説明するテキスト文字列。テンプレートのパラメータ等を入力する際に表示される
    
  • Metadata(オプション)
    テンプレートに関する追加情報を提供する
    
  • Parameters(任意)
    実行時にユーザに入力を求めるパラメータを定義する。インフラ構成は変えず、IPやインスタンスタイプを変えたいなどの運用で便利。
    
  • Mappings(任意)
    HashTableのようなもの。条件パラメータ値の指定に使用できる。リージョンやユーザ入力パラメータによって値が変わるものに利用する。Mappingsを利用することでテンプレートの再利用性が向上する
    
  • 条件(オプション)
    条件つきのリソースの制御が可能
    
  • 変換
    サーバレスアプリケーションの場合は、使用するAWS SAMのバージョンを指定する
    
  • Resources(必須)
    インスタンスやS3など、使用するリソースを指定する。
    
  • Output(任意)
    スタック構築後にCloudFormationから出力させる値(DNS名やEIP値など)
    

テンプレート作成のためのいくつか注意点(共通)

  1. CloudFormation Designerでアイコンを並べていくだけではテンプレートとして機能しない
  2. テンプレート作成し保存しただけではCloudFormationは実行できない
  3. 立ち上げたいインフラ環境のすべてをひとつのテンプレートとして作成すべきではない

1. CloudFormation Designerに関わらず、CloudFormationテンプレートではリソース同士の「依存関係」が重要になる

  • ここで言う依存関係とは、リソースを作成する順序
  • AリソースがBリソースを依存関係に指定している場合、Bリソースが作成されるまでAリソースは作成されない
  • この依存関係は、リソースを構築する上で避けて通ることができない
    ※備考※
    VPC内の一部リソースはゲートウェイを必要とする。
    VPC、ゲートウェイ、ゲートウェイアタッチメントを AWS CloudFormation テンプレートで定義する場合、
    ゲートウェイを必要とするリソースはすべて、そのゲートウェイアタッチメントに依存することになる。
    たとえば、パブリック IP アドレスが割り当てられている Amazon EC2 インスタンスは、
    同じテンプレートで VPC リソースと InternetGateway リソースも宣言されている場合、VPC ゲートウェイのアタッチメントに依存する。
    

現在、次のリソースは関連付けられたパブリック IP アドレスを持ち、VPC 内にある場合VPC ゲートウェイのアタッチメントに依存する。(2017年4月現在)

・Auto Scaling グループ
・Amazon EC2 インスタンス
・Elastic Load Balancing ロードバランサー
・Elastic IP アドレス
・Amazon RDS データベースインスタンス
・インターネットゲートウェイを含む Amazon VPC のルート

2. テンプレート作成後、スタックとして成立させる

  • パラメータを設定すること

    インスタンスタイプなどの値はスタック作成時に設定するケースが多い
    

3. 削除したくないリソースを予め決定しておくこと

  • CloudFormationで起動させたインフラ環境を停止する場合、インフラ環境を削除することになる

    削除対象のテンプレート内に固定IPが割り振られてたインスタンスがあった場合、インフラ環境を再構築した際に割り振りが変更してしまう。
    普遍なリソースはテンプレートを別にする
    

参考

AWS Cloudformationユーザガイド
http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html

CloudFormation超入門
http://dev.classmethod.jp/beginners/chonyumon-cloudformation/

AWS Black Belt Tech シリーズ 2015 – AWS CloudFormation
https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-tech-2015-aws-cloudformation?qid=9c031227-6000-4a12-bf18-49cda2c6bfe9&v=&b=&from_search=1

続きを読む

個人的mastodonの出来事(技術編)

何の話か

結果的に中規模インスタンス(一応某リストで上位100以内にはいっているから現状間違ってないはず)を4台立てることになったので、その経緯とかそこで知ったこととか書きます。

得られた知見

総括として、

  • 100-300人程度であればDBあたりはまだネックにならずメモリが真っ先に悲鳴をあげた(1GBしかない場合)
  • 100人超えると何もいじってないt2.microだときつい
  • 300人超えたくらいでもt2.smallでとりあえずスペック的には動く

ただし、金銭的な都合でRDSやElastiCache、複数EC2による冗長構成などをとっていないために、インスタンスサイズを変更する時など絶対にサービスが止まるので、そこは今回妥協しています。
また、CloudFrontやS3を使うこともできるのですがこちらも金銭的事情と今の所なんとかなっているので使ってないです。

環境としては

  • mailにmailgun
  • SSL証明書にLet’s Encrypt
  • サーバーはだいたいAWS
  • 最低限のメトリクス監視はmackerel

です。

mastodonをはじめたところ

4/12に多分Twitterでみかけてmastodonを始めた。現mstdn.jpインスタンスでアカウントを作りました。
しばらく楽しくやってたのですが、どんどん人が増えるのでコンセンプト的には分散させたほうがいいよなーという思いとか、mastodonで知り合った人にボドゲインスタンスほしいって言われたとか、この勢いなら人くるんじゃね?という企みとかそういうのがあって立てようかなと考え始めました。
結果、4/12は見送ったものの、4/13にmstdn.jpが不安定なこともあり分散する流れになるだろうと判断してそれなら急ごうとその夜から構築を開始することにしました。

1台目 tablegame.mstdn.cloud

どうせAWSとか使うと金銭的に維持できないと思い、家にあったサーバーを流用することに。
帰りにLANカードとバカHUBを買って帰宅。
マシン構成としては

  • ubuntu 14.04 LTS
  • CPU: Sempron 145
  • RAM: 4GB

と非常に貧弱ながらも、ジャンルがニッチなのでこれで試そうと突っ込みました。
ちなみに、Dockerは使っておらず、理由としてはDockerにそこまで明るくないので死活監視とかDB周りをいじる必要が出た時にうまく弄れる自信がなかったためです。

構築で苦労したことは、LANとFletsのPPPoE直の物理インタフェース2本構成になったので、そのroutingとかiptablesわすれたとかその辺です。
また、設定を間違えたままresorceをbuildしたところ、アプリの設定など以外の動的に変わる部分(メインのタイムラインですね)が何も表示されない事態に陥り、そこで困ったりしました。
消してrebuildしたら治りましたが。
あとは、ubuntu 14.04なのでデフォルトのdaemon化する設定が使えず、現状nohupで動かしています。streamのnodeがよく落ちるのでそこだけpythonで監視して落ちてれば再起動するスクリプトをcronで回すという手荒い対応です。

結果として現在300人弱ですが、快適に動いています。

2台目 socialgame.mstdn.cloud

思ったよりうまく動いて、昼休みに先輩と喋っていた勢いで2台目を作りました。
こちらはジャンル的に人が集まる可能性もあったので、AWSを使って、でもまずは最小構成でということでt2.microにすべて載せる構成で作りました。

こちらはUbuntu 16.04なので公式ドキュメント通りに構築しました。(ただしこちらも非Dockerで)

ちゃんと動いたのは良かったものの、その日の夜にメモリを使いきりswapし始めた途端にアクセスさえ困難な状況に。
この時、sshで入れない状況だったのですが公式ドキュメント通りに作っていたのが功をなし、AWSコンソールから直接停止、インスタンスサイズアップで難を逃れました。

結果t2.small一台で現状300人超えたくらいですが、快適に動いています。

3台目, 4台目

2台目を参考に構築しました。4台目のR18可能なソシャゲインスタンス構築の話についてはできれば別の記事で書きます。

技術的タスク

  • バージョンアップが早いのでバージョンアップをどう実施していくか。
  • SSL証明書の更新やLet’s Encryptについてもっと知る
  • mailgunが一部制限かかっているのでそれの対応

最後に

タスク整理とかもあったので書いてみたんですが、これ、得られる情報ありますかね。。。
とりあえずお一人様や100人未満のインスタンスなら多分t2.microで動くと思うので無料枠でお試しとかするといいですよ、くらいかもしれないです。

続きを読む