AmazonAthenaをCLIから使う

よーやくCLIから使えるようになりました。
早速試しましょう

前提条件

  • S3にQuery用のダミーデータが入っているものとします
  • S3にAthenaの実行結果保存用ディレクトリが作成されているものとします
  • Athenaは us-east-1 を使用して実行しています

実行環境

コマンド
aws --version
結果
aws-cli/1.11.89 Python/3.5.0 Darwin/16.5.0 botocore/1.5.52

手元にコマンドがない場合はアップデートしましょう

アップデート
sudo pip install -U awscli --ignore-installed six
  • 筆者の環境ではDatapipelinを使用してDynamoDBのあるテーブルデータをS3にバックアップしたものをサンプルデータとして使用しています。
  • DATABASEに関しては以前Javaからの接続の際検証で作成した dynamodb という名前のDATABASEが存在します。
  • テーブルとして、DynamoDBのJSONデータに対応するように作成した dynamodb.sample1 を使用します。

Queryの実行

コマンド
aws athena start-query-execution 
    --query-string 'select count(*) from dynamodb.sample1;' 
    --result-configuration Database=dynamodb 
    --result-configuration OutputLocation=s3://xxxxxxxxxxxxxxxxxxxxxxxxxxx/cli-test/
{
    "QueryExecutionId": "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
}

結果の取得

コマンド
aws athena get-query-results 
    --query-execution-id "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
結果
{
    "ResultSet": {
        "ResultSetMetadata": {
            "ColumnInfo": [
                {
                    "CatalogName": "hive",
                    "Name": "_col0",
                    "Scale": 0,
                    "SchemaName": "",
                    "Precision": 19,
                    "Type": "bigint",
                    "CaseSensitive": false,
                    "Nullable": "UNKNOWN",
                    "Label": "_col0",
                    "TableName": ""
                }
            ]
        },
        "Rows": [
            {
                "Data": [
                    {
                        "VarCharValue": "_col0"
                    }
                ]
            },
            {
                "Data": [
                    {
                        "VarCharValue": "500000"
                    }
                ]
            }
        ]
    }
}

実行したQueryの詳細を取得する

コマンド
aws athena get-query-execution 
    --query-execution-id  "xxxxxxxxxxxxxxxxxxxxxxxxxxx"
結果
{
    "QueryExecution": {
        "Status": {
            "SubmissionDateTime": 1495440535.706,
            "State": "SUCCEEDED",
            "CompletionDateTime": 1495440549.995
        },
        "QueryExecutionId": "xxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "Statistics": {
            "DataScannedInBytes": 71512882,
            "EngineExecutionTimeInMillis": 13930
        },
        "Query": "select count(*) from dynamodb.sample1",
        "ResultConfiguration": {
            "OutputLocation": "s3://xxxxxxxxxxxxxxxxxxxxxxxxxxx/cli-test/xxxxxxxxxxxxxxxxxxxxxxxxxxx.csv"
        }
    }
}

格納先のオブジェクトパスが取得できます。

これでAthenaを簡単に使える!東京リージョンまだだけど、S3を介してやり取り出来るのでまあまあ使えるんじゃないかという所感。

続きを読む

aws周りのメモ2

postgresqlを使う

RDSへpostgresqlをいれて立ち上げ

認証と接続

import-key-pair — AWS CLI 1.11.87 Command Reference
http://docs.aws.amazon.com/cli/latest/reference/ec2/import-key-pair.html

cd $HOGE
openssl genrsa -out my-key.pem 2048
openssl rsa -in my-key.pem -pubout > my-key.pub
# IAMのコンパネで*.pubを入力
# 多分、権限があれば以下でもいける
# aws iam upload-ssh-public-key

【AWS 再入門】EC2 + RDS によるミニマム構成なサーバー環境を構築してみよう – NET BIZ DIV. TECH BLOG
https://tech.recruit-mp.co.jp/infrastructure/retry-aws-minimum-vpc-server-environment/

便利

無料枠

無料のクラウドサービス | AWS 無料利用枠
https://aws.amazon.com/jp/free/

AMI

AWS Marketplace: Search Results
https://aws.amazon.com/marketplace/search/results?x=14&y=18&searchTerms=&page=1&ref_=nav_search_box

CFテンプレート

サンプルコード & テンプレート – AWS CloudFormation | AWS
https://aws.amazon.com/jp/cloudformation/aws-cloudformation-templates/

ec2 ami tool & ec2 api tool

Mac で Amazon EC2 API Toolsを設定する – サーバーワークスエンジニアブログ
http://blog.serverworks.co.jp/tech/2013/01/31/mac-amazon-ec2-api-tools-setup/

ec2 api toolは若干心配。

VPCを使う

接続の際に、sshを経由したい。sslでもいいけどなんかsshがいいなと。
パスワードよりkeyのほうがセキュアだからかな。

0から始めるAWS入門①:VPC編 – Qiita
http://qiita.com/hiroshik1985/items/9de2dd02c9c2f6911f3b

導入

Amazon VPC とは? – Amazon Virtual Private Cloud
http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_Introduction.html

公式のいろいろ

料金 – Amazon VPC | AWS
https://aws.amazon.com/jp/vpc/pricing/

基本は無料だけどNATとVPNは別課金。

【AWS 再入門】VPC 環境に踏み台サーバーを構築して SSH 接続してみよう – NET BIZ DIV. TECH BLOG
https://tech.recruit-mp.co.jp/infrastructure/retry-aws-bastion-host-vpc/#i-3

ec2(Bastion)を配置する必要がありそう。

【AWS 再入門】EC2 + RDS によるミニマム構成なサーバー環境を構築してみよう – NET BIZ DIV. TECH BLOG
https://tech.recruit-mp.co.jp/infrastructure/retry-aws-minimum-vpc-server-environment/

VPC に推奨されるネットワーク ACL ルール – Amazon Virtual Private Cloud
http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_Appendix_NACLs.html

vpcでのネットワークのポリシーの例

Default VPC

AWSのDefault VPCを削除して困った話 – MikeTOKYO Developers
http://blog.miketokyo.com/post/49939300091/aws-default-vpc

デフォルトvpcは削除したらダメか。使い分けがわからん。

Amazon EC2 と Amazon Virtual Private Cloud – Amazon Elastic Compute Cloud
http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/using-vpc.html

基本の機能はデフォルトとそうじゃないvpcは同じだけど、
デフォルトvpcがないとちゃんと機能しない。
デフォルトの属性によって、指定がないとipを紐付けたりする。

VPCネットワーク設計

これだけ押さえておけば大丈夫!Webサービス向けVPCネットワークの設計指針 | eureka tech blog
https://developers.eure.jp/tech/vpc_networking/

ネットワークは一度稼働させると移行が大変なので、初期設計が非常に重要になります。

わかりやすい。図が特に。

  • Bastion
  • NAT
  • Security Group

ENI

インフラエンジニアに贈るAmazon VPC入門 | シリーズ | Developers.IO
http://dev.classmethod.jp/series/vpcfor-infra-engineer/

サブネットで指定したIPアドレスのうち、先頭4つと末尾の1つはVPCで予約されるため使用できません。

VPCでは常にDHCP有効とするのがポイントです。

また、DHCPサービスで伝えられる情報(DHCPオプション)は、変更することもできます。

仮想マシンにひもづくENIにより、DHCPサーバーから毎回同じMACアドレス、IPアドレスが付与されます。これは、仮想マシンの状態に依存しないため、仮想マシンを再起動しようと、一旦シャットダウンしてしばらくしてから起動した場合でも必ず同じアドレスが付与されます。

ENI(Elastic Network Interface)か。。なるほど。でも、使うことはなさそうだな。

NAT

IPマスカレードが使えないVPC
NATは、Static(静的・サーバー用途)とElastic(仮想・クライアント用途)がある。
個人的には、このNATインスタンスの実装は、あまり好きではありません。動きがややこしいですし、ユーザーが自分でNATインスタンスの管理をしなければならないのも煩雑な印象を受けます。VPCのネットワークサービスの一つとして提供される機能であれば、ユーザーからはなるべく抽象化され仮想マシンとして意識されないようにするべきと考えます。
ただ、ユーザーから仮想マシンとして見える分、機能・実装が具体的に把握できる点やカスタマイズ性が高い点は良いとも思っています。

NAT インスタンスと NAT ゲートウェイの比較 – Amazon Virtual Private Cloud
http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/vpc-nat-comparison.html

なるほど。。

Bastion

AWSで最低限セキュアな構成を組む – Qiita
http://qiita.com/ausuited/items/09b626fa5264f0c650fd

パブリックSubnetにEC2インスタンス(踏み台サーバーとして)
NATインスタンスを作成した要領で、パブリックSubnetにEC2インスタンスを作成する。Security groupは新規に作成してSSHをAnywhereに。Key pairは厳重に管理。尚、踏み台サーバーは、使用する時以外はStoppedにしておく事で、さらにセキュアな状態とする。このデザインパターンをOn Demand Bastionパターンと呼ぶらしい。

詳しい。「On Demand Bastionパターン」か。なるほど。

vpcへの踏み台サーバー
ポートフォワーディング、トンネルなどと同じ意味。

Network ACL

インスタンス単位じゃなくサブネット単位でより制限してセキュアにしたい場合に使うのかな。

安全なVPC設計 — Commerce Hack
http://tech.degica.com/ja/2016/01/07/designing-vpc-and-subnets/


結局どうするのか、、ひとまずNATはつかわずに、Bistionをつくってみる感じかな。

アベイラビリティーゾーン

リージョンごとでの、障害などで全部やられないように物理的にセグメントされた範囲の単位かな。
RDSではセグメントグループに2つ以上のゾーンを含める。でも、一つしか使わなくていい。ということか。s

RDSのVPC間の移動

サブネットグループの関連付けを変えればいいらしい。間違って設定したので移動した。

【小ネタ】知っていましたか?RDSを別のVPCに移動できることを | Developers.IO
http://dev.classmethod.jp/cloud/aws/rds_can_move_to_another_vpc/

Bastion作成作業をしてみる

主に下記を参考。

【AWS 再入門】EC2 + RDS によるミニマム構成なサーバー環境を構築してみよう – NET BIZ DIV. TECH BLOG
https://tech.recruit-mp.co.jp/infrastructure/retry-aws-minimum-vpc-server-environment/

  • サブネットってなんだっけとか復習。
  • ストレージはどうするのか。
    • とりあえずssdにしたけどマグネティックでよかったかなあ。

      • ssd:$0.12 : 1 か月にプロビジョニングされたストレージ 1 GB あたり
      • マグネティック: 0.05 USD/GB-月
  • public IPは設定必要だよね
  • market placeからamiを取得した方がいいの?
    • とりあえず公式のウィザードを使ったけど。
  • 認証にIAMが追加されていたので使ってみた
    • これとは別にキーペアは必要ってことかな。
  • CFnテンプレート(CloudFormationテンプレート)というのがあるらしい。。
    • これでつくりなおそうかな。。
  • サブネットとかいろいろネットワーク系の設定
    • なんだかんだいっていろいろあった
  • セキュリティグループ
    • エイリアスみたいなセキュリティグループにできたらいいのに。タグや名前で明示化かな。
    • bastionは22をあけて、rdsは5432をbastionからのみあける
  • ログイン
  • DNS
    • あれ、パブリックDNSがうまく割り振ってないな。。
      AWSでPublic DNS(パブリックDNS)が割り当てられない時の解決法 – Qiita
      http://qiita.com/sunadoridotnet/items/4ea689ce9f206e78a523
    • RDSのDNS
      • nslookupしたら内部ipがかえってくるのね。接続できないけどなんか気持ち悪いな。これかな。。
        外部からdnsを引けることを気にしている人は見かけなくて便利だからって話なのかね。
        【AWS】VPC内でPrivate DNSによる名前解決 – Qiita
        http://qiita.com/y_takeshita/items/2eb5e6abb5eb5516d1de

やってるうちはいいけど、しばらくやらないと設定の方法とか忘れそう。。こういうのは学習コストだけじゃないな。

PlantUMLで図にしておく

Kobito.hQIwJs.png

VPC内のRDSへLambdaから接続。。

しまった!アンチパターンだそうだ。。

Lambda+RDSはアンチパターン – Qiita
http://qiita.com/teradonburi/items/86400ea82a65699672ad

Lambda + RDS benchmark – Qiita
http://qiita.com/taruhachi/items/3f95ae3e84f56edb3787

新し目の記事でIAM認証でクリアできそうな。。

【全世界待望】Public AccessのRDSへIAM認証(+ SSL)で安全にLambda Pythonから接続する – サーバーワークスエンジニアブログ
https://blog.serverworks.co.jp/tech/2017/04/27/rds-iam-auth-lambda-python/


セキュアに接続するのと速度のトレードオフになっていたのが
IAM認証のおかげで両方可能になったということっぽい。
でも、ネットのスループット、コネクション数(料金・負荷)、など、、ほかにも気にすることが出て来そうで若干不安。
非同期でよければキューイングして一回投げっぱなしすればどうだろう。
もしくは、似てるけど、Lambdaから一回値を返してもらってからRDSへ投げ直す。
これでいっかなあ。。Lambdaの意味がなくなる?うーん。

今後

  • 疑問としてrdsなど内部向けのdnsを外から見れなくできないものか。
  • というか、rdsのエンドポイントって再起動したら変わったりしないかね。ipは固定されるのか。
    • たぶん、サブネット内でdhcpになるのでipは変動するけどエンドポイントは固定。。じゃないかしら。

posgresqlをつかうための情報

Amazon RDS 上の PostgreSQL – Amazon Relational Database Service
http://docs.aws.amazon.com/ja_jp/AmazonRDS/latest/UserGuide/CHAP_PostgreSQL.html#PostgreSQL.Concepts.General.SSL

続きを読む

AWS ECSにてカスタムしたredmineのdockerイメージを動かすまでのメモ(その1)

redmineの構築、プラグインの導入をふつーにやると面倒くさい。
あと、一旦構築した後redmineのバージョンをあげるのもやっぱり面倒くさい。

→ので、dockerにてプラグインのインストールやらなにやらを手順をコード化して簡単にRedmineの導入が
できるようにしました。
なお動作環境はAWSのECS(Elastic Container Service)を使います。

大きな流れ

1.Dockerfileを用意
2.AWSにてElastic Container Service(ECS)のタスクを定義
3.ECSのインスタンスを用意

今回はまず1を用意します。

1.Dockerfileを用意

redmineの公式イメージがdockerhubにあるのでこれをもとにpluginを導入する手順を
dockerfile化していきます。

ポイントは2つです。
1.ベースのイメージはredmine:x.x.x-passengerを利用する
2.DBのマイグレーションが必要ないものはpluginsフォルダに配置し、マイグレーションが必要なものは別フォルダ(install_plugins)に配置。
→コンテナ起動時にマイグレーションを行うようdocker-entrypoint.shに記載しておきます。

インストールするプラグイン一覧

独断と偏見で入れたプラグインです。

No プラグインの名前 概要
1 gitmike githubの雰囲気のデザインテーマ
2 backlogs スクラム開発でおなじみ。ストーリーボード
3 redmine_github_hook redmineとgitを連動
4 redmine Information Plugin redmineの情報を表示可能
5 redmine Good Job plugin チケット完了したら「Good Job」が表示
6 redmine_local_avatars アイコンのアバター
7 redmine_startpage plugin 初期ページをカスタマイズ
8 clipboard_image_paste クリップボードから画像を添付できる 
9 Google Analytics Plugin 閲覧PV測定用
10 redmine_absolute_dates Plugin 日付を「XX日前」 ではなくyyyy/mm/ddで表示してくれる
11 sidebar_hide Plugin サイドバーを隠せる
12 redmine_pivot_table ピボットテーブルできる画面追加
13 redmine-slack 指定したslack channnelに通知可能
14 redmine Issue Templates チケットのテンプレート
15 redmine Default Custom Query 一覧表示時のデフォルト絞り込みが可能に
16 redmine Lightbox Plugin 2 添付画像をプレビューできる
17 redmine_banner Plugin 画面上にお知らせを出せます
18 redmine_dmsf Plugin フォルダで文書管理できる
19 redmine_omniauth_google Plugin googleアカウントで認証可能
20 redmine view customize Plugin 画面カスタマイズがコードで可能

Dockerfile

上記のプラグインをインストール済みとするためのDockerfileです。
なお、ベースイメージは最新(2017/05/19時点)の3.3.3を使用しています。

Dockerfile
FROM redmine:3.3.3-passenger
MAINTAINER xxxxxxxxxxxxxxxxx

#必要コマンドのインストール
RUN apt-get update -y 
 && apt-get install -y curl unzip ruby ruby-dev cpp gcc libxml2 libxml2-dev 
  libxslt1-dev g++ git make xz-utils xapian-omega libxapian-dev xpdf 
  xpdf-utils antiword catdoc libwpd-tools libwps-tools gzip unrtf 
  catdvi djview djview3 uuid uuid-dev 
 && apt-get clean

#timezoneの変更(日本時間)
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
ENV RAILS_ENV production

#gitmakeテーマのインストール
RUN cd /usr/src/redmine/public/themes 
 && git clone https://github.com/makotokw/redmine-theme-gitmike.git gitmike 
 && chown -R redmine.redmine /usr/src/redmine/public/themes/gitmike



#redmine_github_hookのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/koppen/redmine_github_hook.git

#Redmine Information Pluginのインストール
RUN curl http://iij.dl.osdn.jp/rp-information/57155/rp-information-1.0.2.zip > /usr/src/redmine/plugins/rp-information-1.0.2.zip 
 && unzip /usr/src/redmine/plugins/rp-information-1.0.2.zip -d /usr/src/redmine/plugins/ 
 && rm -f /usr/src/redmine/plugins/rp-information-1.0.2.zip

#Redmine Good Job pluginのインストール
RUN curl -L https://bitbucket.org/changeworld/redmine_good_job/downloads/redmine_good_job-0.0.1.1.zip > /usr/src/redmine/plugins/redmine_good_job-0.0.1.1.zip 
 && unzip /usr/src/redmine/plugins/redmine_good_job-0.0.1.1.zip -d /usr/src/redmine/plugins/redmine_good_job 
 && rm -rf /usr/src/redmine/plugins/redmine_good_job-0.0.1.1.zip

#redmine_startpage pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/txinto/redmine_startpage.git

#Redmine Lightbox Plugin 2 Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/peclik/clipboard_image_paste.git

#Google Analytics Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/paginagmbh/redmine-google-analytics-plugin.git google_analytics_plugin

#redmine_absolute_dates Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/suer/redmine_absolute_dates

#sidebar_hide Pluginのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/bdemirkir/sidebar_hide.git

#redmine_pivot_tableのインストール
RUN cd /usr/src/redmine/plugins 
 && git clone https://github.com/deecay/redmine_pivot_table.git

#redmine-slackのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/sciyoshi/redmine-slack.git redmine_slack

#Redmine Issue Templates Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/akiko-pusu/redmine_issue_templates.git redmine_issue_templates

#Redmine Default Custom Query Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/hidakatsuya/redmine_default_custom_query.git redmine_default_custom_query

#Redmine Lightbox Plugin 2 Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/paginagmbh/redmine_lightbox2.git redmine_lightbox2

#redmine_banner Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/akiko-pusu/redmine_banner.git redmine_banner

#redmine_dmsf Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/danmunn/redmine_dmsf.git redmine_dmsf

#redmine_omniauth_google Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/yamamanx/redmine_omniauth_google.git redmine_omniauth_google

#redmine_omniauth_google Pluginのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/onozaty/redmine-view-customize.git view_customize

#redmine_local_avatars用モジュールを用意(インストールはredmine起動時に実施)
RUN cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/ncoders/redmine_local_avatars.git

#backlogsのインストール用モジュールを用意(インストールはredmine起動時に実施)
RUN mkdir /usr/src/redmine/install_plugins 
 && cd /usr/src/redmine/install_plugins 
 && git clone https://github.com/AlexDAlexeev/redmine_backlogs.git 
 && cd /usr/src/redmine/install_plugins/redmine_backlogs/ 
 && sed -i -e '11,17d' Gemfile

#database.ymlファイルを置くフォルダを用意
RUN mkdir /config
COPY docker-entrypoint.sh /
RUN chmod +x /docker-entrypoint.sh

ENTRYPOINT ["/docker-entrypoint.sh"]

EXPOSE 3000
CMD ["passenger", "start"]

docker-entrypoint.sh

次にdocker-entrypoint.shファイルです。
githubに公開されているファイル
(https://github.com/docker-library/redmine/blob/41c44367d9c1996a587e2bcc9462e4794f533c15/3.3/docker-entrypoint.sh)
を元にプラグインのインストールを行うコードを記載していきます。

docker-entrypoint.sh
#!/bin/bash
set -e

case "$1" in
    rails|rake|passenger)
        if [ -e '/config/database.yml' ]; then
                    if [ ! -f './config/database.yml' ]; then
                echo "use external database.uml file"
                ln -s /config/database.yml /usr/src/redmine/config/database.yml
            fi
        fi
                if [ -e '/config/configuration.yml' ]; then
                        if [ ! -f './config/configuration.yml' ]; then
                                echo "use external configuration.uml file"
                                ln -s /config/configuration.yml /usr/src/redmine/config/configuration.yml
                        fi
                fi
        if [ ! -f './config/database.yml' ]; then
            if [ "$MYSQL_PORT_3306_TCP" ]; then
                adapter='mysql2'
                host='mysql'
                port="${MYSQL_PORT_3306_TCP_PORT:-3306}"
                username="${MYSQL_ENV_MYSQL_USER:-root}"
                password="${MYSQL_ENV_MYSQL_PASSWORD:-$MYSQL_ENV_MYSQL_ROOT_PASSWORD}"
                database="${MYSQL_ENV_MYSQL_DATABASE:-${MYSQL_ENV_MYSQL_USER:-redmine}}"
                encoding=
            elif [ "$POSTGRES_PORT_5432_TCP" ]; then
                adapter='postgresql'
                host='postgres'
                port="${POSTGRES_PORT_5432_TCP_PORT:-5432}"
                username="${POSTGRES_ENV_POSTGRES_USER:-postgres}"
                password="${POSTGRES_ENV_POSTGRES_PASSWORD}"
                database="${POSTGRES_ENV_POSTGRES_DB:-$username}"
                encoding=utf8
            else
                echo >&2 'warning: missing MYSQL_PORT_3306_TCP or POSTGRES_PORT_5432_TCP environment variables'
                echo >&2 '  Did you forget to --link some_mysql_container:mysql or some-postgres:postgres?'
                echo >&2
                echo >&2 '*** Using sqlite3 as fallback. ***'

                adapter='sqlite3'
                host='localhost'
                username='redmine'
                database='sqlite/redmine.db'
                encoding=utf8

                mkdir -p "$(dirname "$database")"
                chown -R redmine:redmine "$(dirname "$database")"
            fi

            cat > './config/database.yml' <<-YML
                $RAILS_ENV:
                  adapter: $adapter
                  database: $database
                  host: $host
                  username: $username
                  password: "$password"
                  encoding: $encoding
                  port: $port
            YML
        fi

        # ensure the right database adapter is active in the Gemfile.lock
        bundle install --without development test
        if [ ! -s config/secrets.yml ]; then
            if [ "$REDMINE_SECRET_KEY_BASE" ]; then
                cat > 'config/secrets.yml' <<-YML
                    $RAILS_ENV:
                      secret_key_base: "$REDMINE_SECRET_KEY_BASE"
                YML
            elif [ ! -f /usr/src/redmine/config/initializers/secret_token.rb ]; then
                rake generate_secret_token
            fi
        fi
        if [ "$1" != 'rake' -a -z "$REDMINE_NO_DB_MIGRATE" ]; then
            gosu redmine rake db:migrate
        fi

        chown -R redmine:redmine files log public/plugin_assets

        if [ "$1" = 'passenger' ]; then
            # Don't fear the reaper.
            set -- tini -- "$@"
        fi
                if [ -e /usr/src/redmine/install_plugins/redmine_backlogs ]; then
                        mv -f /usr/src/redmine/install_plugins/redmine_backlogs /usr/src/redmine/plugins/
                        bundle update nokogiri
                        bundle install
                        bundle exec rake db:migrate
                        bundle exec rake tmp:cache:clear
                        bundle exec rake tmp:sessions:clear
            set +e
                        bundle exec rake redmine:backlogs:install RAILS_ENV="production"
                        if [ $? -eq 0 ]; then
                echo "installed backlogs"
                                touch /usr/src/redmine/plugins/redmine_backlogs/installed
            else
                echo "can't install backlogs"
                        fi
            set -e
            touch /usr/src/redmine/plugins/redmine_backlogs/installed
        fi
                if [ -e /usr/src/redmine/install_plugins/redmine_local_avatars ]; then
                        mv -f /usr/src/redmine/install_plugins/redmine_local_avatars /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
                fi
        if [ -e /usr/src/redmine/install_plugins/redmine_slack ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_slack /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_issue_templates ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_issue_templates /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_default_custom_query ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_default_custom_query /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_lightbox2 ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_lightbox2 /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_banner ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_banner /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_dmsf ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_dmsf /usr/src/redmine/plugins/
            bundle install --without development test xapian
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/redmine_omniauth_google ]; then
            mv -f /usr/src/redmine/install_plugins/redmine_omniauth_google /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ -e /usr/src/redmine/install_plugins/view_customize ]; then
            mv -f /usr/src/redmine/install_plugins/view_customize /usr/src/redmine/plugins/
            bundle install --without development test
            bundle exec rake redmine:plugins:migrate RAILS_ENV=production
        fi
        if [ ! -f '/usr/src/redmine/plugins/redmine_backlogs/installed' ]; then
            set +e
            bundle exec rake redmine:backlogs:install RAILS_ENV="production"
            if [ $? -eq 0 ]; then
                echo "installed backlogs"
                touch /usr/src/redmine/plugins/redmine_backlogs/installed
            else
                echo "can't install backlogs"
            fi
            set -e
        fi
        set -- gosu redmine "$@"
                ;;
esac

exec "$@"

次はこのイメージをAWSのECSにて動作させます。
(次回に続く)

続きを読む

Amazon Auroraクラスタへの接続にコネクションプーリングを使うときのフェイルオーバー対応

Amazon Auroraでは、フェイルオーバーが発生してWriterとReaderが切り替わる場合、サーバ自体のIPアドレスは変わらず、クラスタエンドポイントおよび読み取りエンドポイントのFQDN(CNAME相当)が指すIPアドレスが入れ替わります。

また、エンドポイントが指すIPアドレスは、フェイルオーバー発生時に数回フラッピングします。

そのため、アプリケーションサーバからコネクションプーリングを使ってAmazon Auroraに接続していると、意図せずWriterからReaderに「降格」した側のサーバ(レプリカ)に再接続してしまい、更新系クエリがエラーになってしまうことがあります。

※Readerへの接続を意図して誤ってWriterに接続されてしまうことは、ある程度までなら許容できると思いますが。

これを避けるため、コネクションプーリングをやめて都度接続に変更するか、もしくは(Javaであれば)MariaDB Connector/Jのような特別なフェイルオーバー対応処理が入ったコネクターを使ってプーリングすることにするのが一般的だと思いますが、

  • Javaではなく、プーリングがないと速度的にキツい(特に、アプリケーション処理の実装の都合上、コネクションの取得~クローズを細かい単位に区切って行っている場合)
  • Javaだが、細かい挙動がMariaDB Connector/JとMySQL Connector/Jで違うために、MariaDB Connector/Jを採用できない

など、どちらの回避策を取るのも難しい場合の対処法を考えてみました。

コネクションプーリングのValidationクエリ

コネクションプーリング環境では、プールされているコネクションが実際に利用可能かチェックするために、プールからコネクションをBorrowする際のValidationクエリを設定できるのが一般的です。
通常、MySQLでは、Validationクエリに「SELECT 1」(または「/* ping */ SELECT 1」など)を指定しますが、

  • DB接続に使うユーザに「EXECUTE」権限を付与しておく
  • information_schema経由でGLOBAL変数「innodb_read_only」を取得し、「OFF」なら「1」を返し、「ON」なら接続エラーを返すストアドファンクションを定義しておく
  • Validationクエリに↑のストアドファンクションを呼び出すSQLを記述する

という方法で、プールからのBorrow時に、誤ってReaderに接続されたコネクションを閉じて再接続するようにします。

※Tomcatで「Tomcat JDBC Pool」を使う場合は、Validationクエリの代わりにValidation用のクラスを定義して、同じようなことをすることも可能です。

Validation用ストアドファンクションを定義する

まず、ストアドファンクションを設置するデータベースを作成します。DB接続するユーザにEXEC権限も付けておきます。

DB作成(例:concheck)・GRANT
mysql> CREATE DATABASE concheck;
mysql> GRANT EXECUTE ON concheck.* TO '接続ユーザ名'@'IPアドレス範囲等';

バイナリログを有効にしている場合は、非決定的なクエリの実行を許可する状態にします。

バイナリログ有効の場合
mysql> SET GLOBAL log_bin_trust_function_creators = 1;

ストアドファンクションを定義します。

ストアドファンクション
mysql> DELIMITER //
mysql> CREATE FUNCTION concheck.validation() RETURNS INT NOT DETERMINISTIC
    -> BEGIN
    -> SELECT @@GLOBAL.innodb_read_only INTO @flag;
    ->   IF @flag = 'OFF' THEN
    ->     RETURN 1;
    ->   ELSE
    ->     SIGNAL SQLSTATE '08S01'
    ->       SET MESSAGE_TEXT = 'A handshake error occured', MYSQL_ERRNO = 1043;
    ->   END IF;
    -> END;
    -> //
mysql> DELIMITER ;

コネクションプーリングの設定でValidationクエリを指定する

例えば、Apache Commons DBCP(DBCP2)の場合は、「validaitonQuery」に「SELECT concheck.validation()」を指定し、「testOnBorrow」に「true」を指定します。
その他、各種タイムアウト値(「validationQueryTimeout」、「maxWaitMillis」など)やプールされているコネクションの検査・異常コネクションの除去に関する設定値も必要に応じて調整します。

※MySQL Connector/Jを使う場合は、接続用URL中に「connectTimeout」および「socketTimeout」を適切に指定します(いずれも単位はミリ秒)。

制限(限界)

使用するコネクションプーリングの種類やサーバの負荷状況等によっては、これだけでは対応が不十分な場合もあります。

例えば、Apache Commons DBCP2では、「maxTotal」や「maxIdle」を大きな値(無制限を示す「-1」を含む)に指定していたとしても、異常コネクションを除去(Eviction)する設定にしていないとフェイルオーバー後のコネクション数が十分に回復しないことがありますし(場合によってはすべてのコネクションがリークした時と同様のロック状態になります)、除去する設定にした場合も、除去を早めるために処理頻度を高くすると、Webアプリケーションサーバの処理が追いつかなくなる(ロック状態ではないが、リクエストがキャンセルされて負荷が下がるまでまともな時間内に応答できない状態になる)ことがあります。

その場合は、異常コネクションの検査・除去については処理頻度を控えめにしておく一方、「二次対応」として「エラーログ等を確認し、必要に応じてWebアプリケーションサーバの再起動やWebアプリケーションのリロードをする」ような外部処理を実装しておくことになると思います。

続きを読む

IAM認証RDS�・非VPC Lambda・API Gateway・CloudFront・WAFで接続元IP制限付きAPIを作成する

主に以下2例に対して情報を少し追加するだけの記事です。

API GatewayのバックをVPC Lambdaにしている場合、APIのくせに返答に時間がかかる場合がある、という問題があります。なので、

  • 非VPC Lambdaから安全にPublic AccessibleなRDSに接続したい
  • かつ、API Gatewayに接続元IP制限をつけたい

というのがやりたいことです。

RDS接続認証でIAMが使えるようになった

執筆時点で公式日本語ドキュメントは無し。

つまりどういうこと?

MySQLの認証プラグインを作ったからRDSイメージにいれておいたよー、という話らしいので見てみる。

select * from plugin;
+-------------------------+-------------+
| name                    | dl          |
+-------------------------+-------------+
| AWSAuthenticationPlugin | aws_auth.so |
+-------------------------+-------------+
1 row in set (0.01 sec)

同じものをPostgreSQLでも作ってくれればPostgreSQLでもIAM認証が使える用になるんですよね? (よく知らないまま希望)

それのなにがうれしいの?

MySQLのuser/password認証がAWS_ACCESS_KEY_IDとAWS_ACCESS_SECRET_KEYに置き換わったような感じです。

……って言っちゃうと「それはそう」案件になるんですが、特定のIAMロールを持つ非VPC Lambdaからのみのアクセスを許可するようなRDS MySQLを作ることができる、というのが特にうれしい点です。

MySQL組み込みの認証機構よりは比較的安全な方法で、RDSをPublic Accessibleにできるようになる、とも言います。RDSがPublic Accessibleなら、Lambdaも非VPCでよくなります。しかもそのLambdaも特定のIAMロールを持つものだけに制限できます。安心だ。

やりたいこと

RDS (IAM & SSL) | AWS Lambda (IAM) | API Gateway (https) | CloudFront (https) | Web Application Firewall

rds-iam.png

の構成を作りたい。社内APIとかですな。なんか登場人物が多いですが、以下の事情があります

  • ソースIP制限をつけたいが、API Gateway自体にソースIP制限機能がない
  • WAFにはあるが、WAFはALBかCloudFrontにしか使えない

よって、

  • CloudFrontをAPI Gatewayの前段に置く
  • WAFをCloudFrontの前段に置く

ことでアクセスをコントロールします。

API Gateway自体に関しては、API Tokenを必須にすることでアクセスを制限します。もちろんCloudFrontからはアクセスできないといけないので、Origin設定でAPI Tokenをヘッダに追加する必要があります。ややこしいなおい。

ちなみに、API Gatewayの認証をIAMにすることもできるんですが、これだとAPIユーザーにAWS v4 Signature Authを実装させることになるので心苦しいです。

うっわめんどくさっ。

やってみよう

RDSをつくる

IAM Database Authentication for MySQL and Amazon Aurora

IAM認証はdb.m1.smallより大きいインスタンスのみでしか使えないので注意。ユーザーの作り方もドキュメントのままですが、

CREATE USER jane_doe IDENTIFIED WITH AWSAuthenticationPlugin as 'RDS' REQUIRE SSL;

DBとLambda間はSSLにしないといけないので、REQUIRE SSLオプションを付けてSSLを強制します。

Lambdaのレンジ:3306のInをAllowしたSecGroupを作る、つってもLambdaのレンジってめっちゃ広そう?
https://docs.aws.amazon.com/ja_jp/general/latest/gr/aws-ip-ranges.html

Lambdaをつくる

試しに作ったLambdaは

from __future__ import print_function

import os

import boto3
import mysql.connector

def lambda_handler(event, context):
    print(event, context)
    token = boto3.client('rds').generate_db_auth_token(
        DBHostname=os.environ['RDS_HOST'],
        Port=3306,
        DBUsername=os.environ['RDS_USER']
    )
    conn = mysql.connector.connect(
        host=os.environ['RDS_HOST'],
        user=os.environ['RDS_USER'],
        password=token,
        database=os.environ['RDS_DATABASE'],
        ssl_verify_cert=True,
        ssl_ca='rds-combined-ca-bundle.pem'
    )
    cursor = conn.cursor()
    cursor.execute('SELECT user FROM mysql.user')
    rows = cursor.fetchall()
    result = [str(x[0]) for x in rows]
    return {'users': result}

DBのユーザーをSELECTして返すだけのものです。

Lambda実行時のIAMロールにIAM Database Authポリシーが必要になります。

Attaching an IAM Policy Account to an IAM User or Role

arn:aws:rds-db:region:account-id:dbuser:dbi-resource-id/database-user-name

ここでMySQLのGRANTONTOを指定しているみたいなものと考える。

別のIAMロールを付けてLambdaを実行してみると、みごとにDB接続エラーが発生する。

Lambdaをzipでまとめる

Dockerfileを作ってzipを生成するナウでヤングな最先端のイカしたアレだぜ。デプロイ時に非VPCを選択。

FROM amazonlinux

RUN yum install -y python27-devel python27-pip zip
RUN pip install --upgrade pip
RUN mkdir /opt/rds-iam-auth /opt/build
COPY ./ /opt/rds-iam-auth/
WORKDIR /opt/rds-iam-auth
RUN pip install wheel
RUN pip install -r requirements.txt -t .
RUN zip -r rds-iam-auth.zip *

CMD cp rds-iam-auth.zip /opt/build

悲しいかなLambdaのamazonlinuxに入っているboto3がIAM RDS authに未対応 (執筆時) だったのでrequirement.txtに追記。近々AMIもアップデートされるだろう。

その他をつくる

コンソールからぽちぽちやる作業がたくさんあります。特にここで追記することはないので、先人の記事を参照したいところだ。

注意点まとめ

  • APIエンドポイントを直接使用されないように、API Tokenを必須にしておくこと。
  • WAFはGlobalリージョンで作成しないとCloudFrontのコンソールから選べないので注意。
  • CloudFrontのOrigin設定時に、x-api-tokenヘッダ転送設定を追加すること。
  • CloudFrontの設定反映には時間がかかるの辛いコーヒー飲むしかない。

ためす

WAFで社内のGlobal IPのみアクセスを許可。

$ curl https://hoge.cloudfront.net/
{"users": ["helloworld", "iamuser1", "mysql.sys", "rdsadmin"]}

WiFi変えたりとかして、Global IPを変えてみると

$ curl https://hoge.cloudfront.net/
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<HTML><HEAD><META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=iso-8859-1">
<TITLE>ERROR: The request could not be satisfied</TITLE>
</HEAD><BODY>
<H1>ERROR</H1>
<H2>The request could not be satisfied.</H2>
<HR noshade size="1px">
Request blocked.
<BR clear="all">
<HR noshade size="1px">
<PRE>
Generated by cloudfront (CloudFront)
Request ID: jIG5TSJUn9rPZYI0JwUr9wZHFHFa_LRyVmwGC302sHjBgv0sLoPubA==
</PRE>
<ADDRESS>
</ADDRESS>
</BODY></HTML>

Forbiddenされる。

まとめ

AWSよくできてんなー。

続きを読む

Rails4+Capistrano3+Nginx+Unicorn EC2へのデプロイ < 後編 >

インフラ勉強中の者がAWSにrailsアプリをEC2にデプロイしたので備忘録として残します。
< 後編 >です。

【イメージ】
スクリーンショット 2017-05-09 11.04.30.png

< 前編 >Unicorn の設定までを実施しました。

デプロイチェック

ローカル
$ bundle exec cap production deploy:check

デプロイチェックをすると下記のようなエラーが出るはずです。

~ 省略 ~
00:02 deploy:check:linked_files
ERROR linked file /var/www/deployApp/shared/config/database.yml does not exist on YOUR_IP_ADDRESS

/var/www/deployApp/shared/config/database.yml が存在しないためのエラーです。

shared ディレクトリは、Capistranoが自動生成するディレクトリです。
EC2の qiitaApp の配下を確認します。

EC2
[myname@ip-10-0-1-86 qiitaApp]$ ls -la
drwxrwxr-x 4 ec2-user ec2-user 4096  5  9 04:08 .
drwxrwxrwx 3 root     root     4096  5  9 04:06 ..
-rw-rw-r-- 1 ec2-user ec2-user    6  5  9 03:33 .ruby-version
drwxrwxr-x 2 ec2-user ec2-user 4096  5  9 04:08 releases ← 自動生成
drwxrwxr-x 7 ec2-user ec2-user 4096  5  9 04:08 shared ← 自動生成

config 配下に database.yml を生成すればエラーは出なくなるのですが、次に同じようにsecrets.ymlが存在しないというエラーが出ますので、共に生成して変更します。

EC2上にdatabase.ymlの生成の前にローカルの database.yml を変更します。

ローカル
$ vi config/database.yml 

======================= database.yml ================================

~ 省略 ~
#production:
#  <<: *default
#  database: db/production.sqlite3
   ↓↓↓
production:
  <<: *default
  adapter: mysql2
  database: qiita_db
  host: qiita_db.xxxxxxxxxxxxxx.ap-northeast-1.rds.amazonaws.com                                                                                                                                                     
  username: qiita_user
  password: YOUR_DB_PASS
  encoding: utf8

====================================================================

変更したら database.yml 全体をコピーして保存します。

EC2
[ec2-user@ip-10-0-1-86 qiitaApp]$ cd shared/config/
[ec2-user@ip-10-0-1-86 config]$ touch database.yml
[ec2-user@ip-10-0-1-86 config]$ vi database.yml

======================= database.yml ===============================

 ローカルでコピーしたdatabase.ymlを貼り付けます。

====================================================================

同様に secrets.yml もローカルの中身をコピーして(変更はなし)、configの配下に secrets.yml を生成して貼り付けます。

EC2
[ec2-user@ip-10-0-1-86 config]$ touch secrets.yml
[ec2-user@ip-10-0-1-86 config]$ vi secrets.yml

再度、デプロイチェックを実施します。

ローカル
$ bundle exec cap production deploy:check

エラーが出なければdeploy:checkは完了です。

デプロイ

ローカル
$ bundle exec cap production deploy --trace #traceをつけることでバグの箇所を発見しやすくします

エラーが出なければデプロイ完了です。

今回は、scaffoldで簡単なUserアプリを生成しているので、 直接、http://IP_ADDRESS/users/ を叩いて確認してみます。

…おそらく500エラーが返ってきてしまって確認できないと思います。

Unicornの状態とエラーログを確認します。

EC2
[ec2-user@ip-10-0-1-86 config]$ ps ax | grep unicorn
30256 ?        Sl     0:01 unicorn master -c /var/www/qiitaApp/current/config/unicorn.rb -E deployment -D                                             
30311 ?        Sl     0:00 unicorn worker[0] -c /var/www/qiitaApp/current/config/unicorn.rb -E deployment -D                                          
30313 ?        Sl     0:00 unicorn worker[1] -c /var/www/qiitaApp/current/config/unicorn.rb -E deployment -D                                          
30316 ?        Sl     0:00 unicorn worker[2] -c /var/www/qiitaApp/current/config/unicorn.rb -E deployment -D                                          
30334 pts/1    S+     0:00 grep --color=auto unicorn

Unicornは稼働していることが確認できます。
次はエラーログです。

EC2
[ec2-user@ip-10-0-1-86 config]$ cd ../..
[ec2-user@ip-10-0-1-86 qiitaApp]$ tail -f current/log/unicorn_error.log 

この状態で再度ローカルからデプロイします。

ローカル
[ec2-user@ip-10-0-1-86 qiitaApp]$ bundle exec cap production deploy
EC2
I, [2017-05-09T06:57:08.516736 #30256]  INFO -- : executing ["/var/www/qiitaApp/shared/bundle/ruby/2.3.0/bin/unicorn", "-c", "/var/www/qiitaApp/current/config/unicorn.rb", "-E", "deployment", "-D", {8=>#<Kgio::UNIXServer:fd 8>}] (in /var/www/qiitaApp/releases/20170509065649)
I, [2017-05-09T06:57:08.735844 #30256]  INFO -- : inherited addr=/tmp/unicorn.sock fd=8
I, [2017-05-09T06:57:08.736108 #30256]  INFO -- : Refreshing Gem list
I, [2017-05-09T06:57:09.978667 #30311]  INFO -- : worker=0 ready
I, [2017-05-09T06:57:09.981219 #30256]  INFO -- : master process ready
I, [2017-05-09T06:57:09.982336 #30313]  INFO -- : worker=1 ready
I, [2017-05-09T06:57:09.985331 #30316]  INFO -- : worker=2 ready
I, [2017-05-09T06:57:10.155786 #29111]  INFO -- : reaped #<Process::Status: pid 29166 exit 0> worker=0
I, [2017-05-09T06:57:10.155874 #29111]  INFO -- : reaped #<Process::Status: pid 29168 exit 0> worker=1
I, [2017-05-09T06:57:10.155945 #29111]  INFO -- : reaped #<Process::Status: pid 29171 exit 0> worker=2
I, [2017-05-09T06:57:10.155973 #29111]  INFO -- : master complete

この時点ではエラーらしきものが出ていません。

再度 http://IP_ADDRESS/users/ を叩いて確認してみます。

EC2
E, [2017-05-09T06:59:27.508190 #30316] ERROR -- : app error: Missing `secret_token` and `secret_key_base` for 'production' environment, set these values in `config/secrets.yml` (RuntimeError)

Missing secret_token and secret_key_base とあります。
EC2上の qiitaApp/current/config/secrets.yml の secret_key_base:の環境変数(SECRET_KEY_BASE)が設定されていないことが原因です。

EC2
[ec2-user@ip-10-0-1-86 qiitaApp]$ cd current
[ec2-user@ip-10-0-1-86 current]$ bundle exec rake secret
1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz ←コピーする
[ec2-user@ip-10-0-1-86 current]$ vi config/secrets.yml 

============================== secrets.yml ==================================

~ 省略 ~
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
          ↓↓↓
  secret_key_base: 1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz

=============================================================================

本来であれば、直接入力するのではなく、

SECRET_KEY_BASE=1234567890abcd...
export SECRET_KEY_BASE

とかで環境変数のまま使用すべきです。
が、何回実施してもググってもうまくいかなかったので、取り急ぎ直接入力で対応します。
dotenvというgemがあるようなので、それで対応してもいいと思います。

再度、ローカルからデプロイして、urlを叩けば下記の画面が表示されると思います。
スクリーンショット 2017-05-09 16.28.33.png

大変参考にさせていただきました。
capistranoを使ってrailsをnginx+unicorn+mysqlの環境にデプロイする
ローカルで開発したRailsアプリをCapistrano3でEC2にデプロイする
Capistrano3でUnicorn+Nginxな環境にRailsをデプロイする:初心者向け
Railsプロダクション環境 Unicornでsecret_key_baseが設定されていないとエラーが出る

続きを読む

Rails4+Capistrano3+Nginx+Unicorn EC2へのデプロイ < 前編 >

インフラ勉強中の者がAWSにrailsアプリをEC2にデプロイしたので備忘録として残します。
長いので<前編><後編>の2回に分けて実施致します。

【前提】
・今回はscaffoldで作成した簡単なアプリ(userの名前と年齢を登録するアプリ)をデプロイすることをゴールに進めていきます。
・デプロイのディレクトリは /var/www/qiitaAppです。
shared/config 配下のdatabase.ymlsecrets.yml は手動で作成します。
・ローカルへのRubyやRailsのインストールとEC2、RDSのセットアップ(VPCやセキュリティグループ等々の設定)、GitHubのアカウント登録に関しての説明は致しません。
・hostの設定、各環境毎の設定は実施しません。

Rubyのインストール
Ruby on Railsのインストールと設定
AWSの設定

【イメージ】
スクリーンショット 2017-05-09 11.04.30.png

【バージョン】

software version
Ruby 2.3.3
Rails 4.2.7
Nginx 1.10.2
Unicorn 5.3.0
MySql 5.6
Capistrano 3.8.1
capistrano3-unicorn 0.2.1

今回は、デプロイ時にUnicornの再起動もさせるべくcapistrano3-unicornというgemを使用します。
(自身でtaskとして設定する事も可能です。)

GitHubの設定

1)New Repositoryの生成
Repository name → qiitaApp
Public
Create repository

2)ローカルからSSH接続ができるように[SSH keys]を追加する
今回は説明は省略します。
(EC2からもGitHub接続が必要なのでそちらは下記に記載します。)
GitHub 初心者による GitHub 入門(1)

EC2(AmazonLinux)の設定

1) git,rbenvのインストール

[myname@ip-10-0-1-86 ~]$ sudo yum -y install git
[myname@ip-10-0-1-86 ~]$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv #rbenvインストール
[myname@ip-10-0-1-86 ~]$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build #ruby-buildインストール
[myname@ip-10-0-1-86 ~]$ sudo vi .bash_profile #.bash_profileの編集

=ファイルの編集画面=

export PATH
export PATH="$HOME/.rbenv/bin:$PATH" ← 追加
eval "$(rbenv init -)" ← 追加

=================

[myname@ip-10-0-1-86 ~]$ source ~/.bash_profile #環境変数の反映
[myname@ip-10-0-1-86 ~]$ rbenv -v #バージョン確認
rbenv 1.1.0-2-g4f8925a

2) Ruby2.3.3のインストール

[myname@ip-10-0-1-86 ~]$ sudo yum install -y gcc
[myname@ip-10-0-1-86 ~]$ sudo yum install -y openssl-devel readline-devel zlib-devel
[myname@ip-10-0-1-86 ~]$ rbenv install 2.3.3

3) Nginxのインストール・設定

[myname@ip-10-0-1-86 ~]$ sudo yum install nginx
[myname@ip-10-0-1-86 ~]$ cd /etc/nginx/conf.d/
[myname@ip-10-0-1-86 conf.d]$ sudo touch local.conf
[myname@ip-10-0-1-86 conf.d]$ sudo vi local.conf

==================== local.conf ===================================

upstream unicorn {
  server unix:/tmp/unicorn.sock;
}

server {
  listen 80;
  server_name YOUR_IP_ADDRESS;

  access_log /var/log/nginx/sample_access.log;
  error_log /var/log/nginx/sample_error.log;

  location / {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://unicorn;
  }
}

=================================================================

4) デプロイするディレクトリと権限の変更

[myname@ip-10-0-1-86 conf.d]$ cd /var/
[myname@ip-10-0-1-86 var]$ sudo mkdir www
[myname@ip-10-0-1-86 var]$ sudo chmod 777 www
[myname@ip-10-0-1-86 var]$ cd www
[myname@ip-10-0-1-86 www]$ mkdir qiitaApp
[myname@ip-10-0-1-86 www]$ cd qiitaApp
[myname@ip-10-0-1-86 qiitaApp]$ rbenv local 2.3.3

5) GitHubへの接続 SSHkey

[myname@ip-10-0-1-86 qiitaApp]$ cd 
[myname@ip-10-0-1-86 ~]$ ssh-keygen -t rsa
$ cat .ssh/id_rsa.pub
ssh-rsa abcdefghijklmnopqrstuvwxyz1234567890 ec2-user@ip-10-0-1-86

[ssh-rsa]から[ec2-user@ip-10-0-1-86]までをコピーしてGitHubのSSH keysにNewKeyとして追加する

5) その他必要なソフトウェアのインストール・設定

  • bundler
[myname@ip-10-0-1-86 ~]$ rbenv exec gem install bundler
[myname@ip-10-0-1-86 ~]$ rbenv rehash
  • Node.js
[myname@ip-10-0-1-86 ~]$ sudo yum install nodejs --enablerepo=epel
  • sqlite
[myname@ip-10-0-1-86 ~]$ sudo yum install sqlite-devel
  • mysql
[myname@ip-10-0-1-86 ~]$ sudo yum install mysql-devel

ローカルの設定

1) railsアプリの生成とgemのインストール

$ rails new qiitaApp
$ cd qiitaApp/
$ git init
$ vi Gemfile

============ Gemfile ===============

gem 'unicorn' #コメントアウトを解除する
gem 'mysql2'                                                                                                                                                                                                       
group :development do
  gem 'capistrano'
  gem 'capistrano-rails'
  gem 'capistrano-bundler'
  gem 'capistrano-rbenv'
  gem 'capistrano3-unicorn'
end

===================================

$ bundle install
$ rails g scaffold user name:string age:integer
$ rake db:migrate

2) Capistranoの設定に必要なファイルの生成

$ bundle exec cap install STAGES=production #自動で下記のディレクトリとファイルを生成
mkdir -p config/deploy
create config/deploy.rb
create config/deploy/production.rb
mkdir -p lib/capistrano/tasks
create Capfile
Capified

3) Capfileの設定

$ vi Capfile #下記を追加
require 'capistrano/rbenv'
require 'capistrano/bundler'
require 'capistrano/rails/assets'
require 'capistrano/rails/migrations'
require 'capistrano3/unicorn'

4) config/deploy.rbの設定

$ vi config/deploy.rb

lock "3.8.1"

set :application, "qiitaApp"
set :repo_url, "git@github.com:YOUR_GITHUB_ACCOUNT/qiitaApp.git"

set :rbenv_type, :user
set :rbenv_ruby, '2.3.3'
set :rbenv_prefix, "RBENV_ROOT=#{fetch(:rbenv_path)} RBENV_VERSION=#{fetch(:rbenv_ruby)} #{fetch(:rbenv_path)}/bin/rbenv exec"
set :rbenv_map_bins, %w{rake gem bundle ruby rails}
set :rbenv_roles, :all

set :log_level, :warn 

# Default value for :linked_files is []
set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml')

# Default value for linked_dirs is []
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')

# Default value for keep_releases is 5
set :keep_releases, 3

set :unicorn_pid, "#{shared_path}/tmp/pids/unicorn.pid"

set :unicorn_config_path, -> { File.join(current_path, "config", "unicorn.rb") }

namespace :deploy do
  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      # within release_path do
      #   execute :rake, 'cache:clear'
      # end
    end
  end
end

after 'deploy:publishing', 'deploy:restart'
namespace :deploy do
  task :restart do
    invoke 'unicorn:restart'
  end
end    

5) config/deploy/production.rbの設定

set :branch, 'master'

server 'EC2-IP-ADDRESS', user: 'ec2-user', roles: %w{app db web} ※

set :ssh_options, {
    keys: %w(~/.ssh/YOUR_EC2_KEY.pem),
    forward_agent: true,
    auth_methods: %w(publickey)
  }

※今回はEC2-userでログインする設定で進めていきます。
本来はデプロイユーザを設定して、そのユーザのみがデプロイ可能にすべきだと思います。

7) Unicornの設定

手動で config 配下に unicorn.rb を生成

$ cd config
$ touch unicorn.rb
$ vi unicorn.rb

========================== unicorn ==============================

APP_PATH   = "#{File.dirname(__FILE__)}/.." unless defined?(APP_PATH)
RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
RAILS_ENV  = ENV['RAILS_ENV'] || 'development'

worker_processes 3

listen "/tmp/unicorn.sock"
pid "tmp/pids/unicorn.pid"

preload_app true

timeout 60
working_directory APP_PATH

# log
stderr_path "#{RAILS_ROOT}/log/unicorn_error.log"
stdout_path "#{RAILS_ROOT}/log/unicorn_access.log"

if GC.respond_to?(:copy_on_write_friendly=)
  GC.copy_on_write_friendly = true
end

before_exec do |server|
  ENV['BUNDLE_GEMFILE'] = APP_PATH + "/Gemfile"
end

before_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.connection.disconnect!

  old_pid = "#{ server.config[:pid] }.oldbin"
  unless old_pid == server.pid
    begin
      Process.kill :QUIT, File.read(old_pid).to_i
    rescue Errno::ENOENT, Errno::ESRCH

    end
  end
end

after_fork do |server, worker|
  defined?(ActiveRecord::Base) and ActiveRecord::Base.establish_connection
end

=================================================================

ここまでで各種設定に関しては終了です。
shared/config 配下のdatabase.ymlsecrets.yml は手動で作成します。
が残っていますが、こちらは後編で実施します。

最後にGitHubにここまでのコードをあげておきます。

$ git add .
$ git commit -m "first commint(任意)"
$ git remote add origin git@github.com:YOUR_GITHUB_ACCOUNT/qiitaApp.git
$ ssh-add ~/.ssh/id_rsa_github
$ git push origin master

前編は以上です。
後編ではデプロイチェック、デプロイ、ログ確認を実施します。

続きを読む

AIDEを使ってファイルの改竄検知を行う。

はじめに

ファイル改竄検知に AIDE(Advanced Intrusion Detection Environment) というのがあります。
オープンソースでAmazon Linuxにもリポジトリが存在するのでyumで簡単に導入することが可能です。

インストール

リポジトリが存在するのでyumコマンドで簡単にインストールできます。

インストール
$ sudo yum install aide

データベースの初期化

インストールできたらまずは初期化を行います。

初期化
$ sudo aide -i

AIDE, version 0.14

### AIDE database at /var/lib/aide/aide.db.new.gz initialized.

初期化が完了すると aide.db.new.gz というファイルが作成されますが、デフォルトの参照ファイルは aide.db.gz であるため、リネームします。

データベース配置
$ sudo cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

改竄チェック

それではチェックしてみましょう。

改竄チェック
$ sudo aide --check

AIDE, version 0.14

### All files match AIDE database. Looks okay!

okayとあるため特に問題はないみたいですがそりゃそうですよね。何もしてないですからw
ということで、 /root 配下に適当にファイルを作成してみます。

ファイル配置
$ sudo touch /root/aide-test.txt

それではもう一度チェックしてみましょう。

改竄チェック
$ sudo aide --check
AIDE found differences between database and filesystem!!
Start timestamp: 2017-04-28 17:36:55

Summary:
  Total number of files:    60760
  Added files:          1
  Removed files:        0
  Changed files:        1


---------------------------------------------------
Added files:
---------------------------------------------------

added: /root/aide-test.txt

---------------------------------------------------
Changed files:
---------------------------------------------------

changed: /root

--------------------------------------------------
Detailed information about changes:
---------------------------------------------------


Directory: /root
  Mtime    : 2017-04-28 17:28:16              , 2017-04-28 17:36:47
  Ctime    : 2017-04-28 17:28:16              , 2017-04-28 17:36:47

先ほど作成したファイルが表示されました。追加/削除/変更が確認できるので便利ですね。

更新

このままだとチェックするたびに変更が増えていってしまいますね。
なのでデータベースを更新する必要があります。更新は初期化してあげればOKです。

データベース更新
$ sudo aide --init

AIDE, version 0.14

### AIDE database at /var/lib/aide/aide.db.new.gz initialized.
データベース更新
$ sudo cp /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

これだと毎回「チェック → 更新」を繰り返さないといけないので面倒です。
そんな場合はupdateオプションを利用すれば解決です。

このオプションはチェックを行なった後、データベースの更新をしてくれます。

オプション

主要なオプションについてまとめました。

オプション 説明
–init または -i データベースの初期化
–check または -C データベースのチェック
–update または -u データベースのチェックと更新

おわりに

これをcronとかで定期実行すればファイル改竄検知が手軽にできてとても便利ですね。

続きを読む

【MySQL】AWS Aurora上でもgh-ostオンラインマイグレーション

【MySQL】gh-ostでオンラインマイグレーションの応用編です。

gh-ostはMySQLのバイナリログを使ってテーブルを同期します。
一方で、AWSのAmazon Auroraは、バイナリログを使わない方法でリードレプリカを作成します。

※Auroraのリードレプリカが一体どういう仕組みなのかここでは省きますが、過去のAWS SummitのDeep DiveとかでAmazonの超技術を垣間見ることができます。
[レポート] Amazon Aurora deep dive ~性能向上の仕組みと最新アップデート~ #AWSSummit | Developers.IO

さてバイナリログを使っていないとなると、gh-ostの利用はどうなるのでしょうか。
gh-ostにもドキュメントがありますが

https://github.com/github/gh-ost/blob/master/doc/rds.md

gh-ost has been updated to work with Amazon RDS however due to GitHub not relying using AWS for databases, this documentation is community driven so if you find a bug please open an issue!

(gh-ostはAmazon RDSでも動くようになったが、我々GitHubはDBにAWSは使ってないので、このドキュメントはコミュニティに懸かっている。だからバグを見つけたら是非issueを上げて欲しい)

とあり、gh-ostの特徴のひとつである「GitHub社での実績」が欠けます。
実際に使ってみました。

準備

下記条件でAWS RDS Auroraインスタンスを作ります。
t2.smallインスタンスが解禁されたので、検証用途でもAuroraを作りやすくなりました。

  • Aurora 5.6.10a
  • db.t2.small
  • writer×1、reader×1

また、接続元のIPアドレスを調べてセキュリティグループでポートを開けておきます。

gh-ostを使うにはバイナリログが必要です。
先述のとおりAuroraのリードレプリカ作成にはバイナリログは使われませんが、Auroraと他のMySQL等とのレプリケーションのためにバイナリログの出力機能は存在しています。

Aurora と MySQL との間、または Aurora と別の Aurora DB クラスターとの間のレプリケーション – Amazon Relational Database Service

gh-ostを利用するには、DB Cluster Parameter Groupにて「binlog_format=ROW」に設定しておく必要があります。

クラスターエンドポイントに接続し、show binary logsでバイナリログファイルが確認できればOKです。

mysql> SHOW VARIABLES LIKE "binlog_format";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| binlog_format | ROW   |
+---------------+-------+
1 row in set (0.01 sec)

mysql> show binary logs;
+----------------------------+-----------+
| Log_name                   | File_size |
+----------------------------+-----------+
| mysql-bin-changelog.000001 |       120 |
| mysql-bin-changelog.000002 |     16062 |
+----------------------------+-----------+
2 rows in set (0.01 sec)

今回の検証には下記のデータベース/テーブルを使います。

USE sushiya;

CREATE TABLE sushi(
  id   INT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20),
  INDEX(id)
);

gh-ostの実行

まず入手します。

$ wget https://github.com/github/gh-ost/releases/download/v1.0.36/gh-ost-binary-linux-20170403125842.tar.gz
$ tar xzvf ./gh-ost-binary-linux-20170403125842.tar.gz

gh-ostには3つのモードがありますが、Aurora上ではb. Connect to masterにて実行します。

$ ./gh-ost 
  --user="(ユーザー)" 
  --password="(パスワード)" 
  --host="(クラスターエンドポイント)" 
  --port=3306 
  --database="sushiya" 
  --table="sushi" 
  --alter="ADD COLUMN price INT DEFAULT 100, ADD COLUMN created_at DATETIME" 
  --allow-on-master 
  --execute

確認します。

mysql> show create table sushiG
*************************** 1. row ***************************
       Table: sushi
Create Table: CREATE TABLE `sushi` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `price` int(11) DEFAULT '100',
  `created_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.01 sec)

カラムが増えていることを確認できました。

gh-ostの特徴としてa. Connect to replica, migrate on masterモードやc. Migrate/test on replicaモードがありますが、これらを使うためにリードレプリカに接続して実行しようとしても「バイナリログが無効」というエラーで失敗してしまいます。

$ ./gh-ost 
>   --user="(ユーザー)" 
>   --password="(パスワード)" 
>   --host="(リーダーエンドポイント)" 
>   --port=3306 
>   --database="sushiya" 
>   --table="sushi" 
>   --alter="ADD COLUMN price INT DEFAULT 100, ADD COLUMN created_at DATETIME" 
>   --test-on-replica
2017-05-02 01:11:40 FATAL (リーダーエンドポイント):3306 must have binary logs enabled

もしAuroraからバイナリログを受け取って同期しているSlaveデータベースがあれば、c. Migrate/test on replicaモードでの実行も可能なのではないかと思います。

Interactive commandsの使用

通常のgh-ost利用時と同様に、UNIXドメインソケットを使った制御もAurora経由で可能です。

gh-ost/interactive-commands.md at master · github/gh-ost

テーブル切り替えを保留にするフラグファイルを作成します。

$ touch /tmp/ghost.postpone.flag

実行します。

$ ./gh-ost 
  --user="(ユーザー)" 
  --password="(パスワード)" 
  --host="(クラスターエンドポイント)" 
  --port=3306 
  --database="sushiya" 
  --table="sushi" 
  --alter="ADD COLUMN price INT DEFAULT 100, ADD COLUMN created_at DATETIME" 
  --allow-on-master 
  --postpone-cut-over-flag-file=/tmp/ghost.postpone.flag 
  --execute

--postpone-cut-over-flag-fileオプションで、先程作成したフラグファイルを指定するとテーブルの同期だけが進み、切り替えが保留状態になります。

ここからステータスの確認など、gh-ost実行途中での操作ができます。

$ echo status | nc -U /tmp/gh-ost.sushiya.sushi.sock

unposeponeでテーブルの切り替えが実行されます。

$ echo unpostpone | nc -U /tmp/gh-ost.sushiya.sushi.sock

確認します。

mysql> SHOW CREATE TABLE sushiG
*************************** 1. row ***************************
       Table: sushi
Create Table: CREATE TABLE `sushi` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL,
  `price` int(11) DEFAULT '100',
  `created_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `id` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.01 sec)

カラムが追加されました。

Auroraでgh-ostの必要性がどれくらいあるかはさておき、Auroraでも一部条件下にてgh-ostを利用できることがわかりました。

参考文献

続きを読む

Amazon RDS のレプリケーションをEC2で行う。

はじめに

RDSのレプリケーションをEC2を使って行いました。
rds-ec2_rep.png

設定

1. binlog_checksumの設定

まずは忘れないうちにパラメータグループの設定を行なってしまいましょう。マスターとなるRDSに対して行う設定です。
RDSにアタッチしてるパラメータグループの binlog_checksumNONE に設定します。
rds-binlog_checksum02.png

もし、これを設定していないで start slave を行うと、

error
Got fatal error 1236 from master when reading data from binary log: 'Slave can not handle replication events with the checksum that master is configured to log; the first event 'mysql-bin-changelog.000008' at 422, the last event read from '/rdsdbdata/log/binlog/mysql-bin-changelog.000008' at 422, the last byte read from '/rdsdbdata/log/binlog/mysql-bin-changelog.000008' at 120.'

というエラーが発生します。

2. レプリケーション用アカウントの作成

パラメータグループの設定が完了したら、次はレプリケーション用のアカウントを作成します。

アカウント作成
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%' IDENTIFIED BY 'hogehoge';
Query OK, 0 rows affected (0.02 sec)

3. rds_heartbeat2の作成

ここからはスレーブとなるEC2のMySQLに対して行います。
EC2のmysqlデータベースにはもちろんrds関連のテーブルは存在しません。
そのため、 rds_heartbeat2 テーブルを作成しておきます。

テーブル作成
mysql> CREATE TABLE `rds_heartbeat2` (`id` int(11) NOT NULL, `value` bigint(20) DEFAULT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=latin1;
Query OK, 0 rows affected (0.01 sec)

ここでテーブルを作成しておかないと start slave した際に以下のようなエラーにが発生するので注意です。

error
Error 'Table 'mysql.rds_heartbeat2' doesn't exist' on query. Default database: 'mysql'. Query: 'INSERT INTO mysql.rds_heartbeat2(id, value) values (1,1493274809584) ON DUPLICATE KEY UPDATE value = 1493274809584'

4. my.cnfの設定

次に、 my.cnf[mysqld] に以下の設定を追加します。

my.cnf
server-id=206
replicate-ignore-db=mysql
オプション 説明
server-id マスターおよびスレーブサーバーが自身を識別するための一意の数字
replicate-ignore-db 無視するデータベースを指定

server-id の数字は任意のもので構いません。 replicate-ignore-db でmysqlデータベースをレプリケーションの対象外としています。

ここで、 replicate-ignore-dbmysql を除外しているから rds_heartbeat2 は作成しなくてもいいのでは?」 と思いますが、ステートメントベースレプリケーションを利用する場合は予期した通りに機能しないという記載があります。

ー 引用:17.1.4.3 レプリケーションスレーブのオプションと変数
ステートメントベースレプリケーションを使用する場合、次の例は予期したとおりに機能しません。スレーブが –replicate-ignore-db=sales で起動され、次のステートメントをマスターで発行するものとします。

USE prices;
UPDATE sales.january SET amount=amount+1000;

このような場合 UPDATE ステートメントは複製されます。–replicate-ignore-db が (USE ステートメントで指定された) デフォルトデータベースにのみ適用されるためです。sales データベースがステートメントで明示的に指定されたため、ステートメントはフィルタされませんでした。しかし、行ベースレプリケーションを使用するときは、UPDATE ステートメントの影響はスレーブに伝達されず、sales.january テーブルのスレーブのコピーは変更されません。この例では、sales データベースのマスターのコピー内のテーブルに加えられたすべての変更は、–replicate-ignore-db=sales によってスレーブで無視されます。

ここに記載されている内容だと、行ベースのレプリケーションに切り替えれば意図した通り除外できるようですが、そうなるとバイナリログの量が増加し、外部とのレプリケーションの場合だと通信料も増加するため注意が必要です。

レプリケーション

1. RDSへのトラフィック停止

まずはRDSに対して流れるトラフィックを全て停止します。また、実行中のプロセスがないか確認もしておきましょう。
ある場合は各プロセスの mysql.rds_kill コマンドを呼び出して、すべてのセッションを閉じます。

プロセスチェック
mysql> show processlist;
+-------+----------+------------------+-------+---------+------+-------------+------------------+
| Id    | User     | Host             | db    | Command | Time | State       | Info             |
+-------+----------+------------------+-------+---------+------+-------------+------------------+
| 52605 | rdsadmin | localhost:26632  | mysql | Sleep   |   13 |             | NULL             |
| 94655 | awsuser  | 10.0.0.206:58658 | NULL  | Query   |    0 | System lock | show processlist |
+-------+----------+------------------+-------+---------+------+-------------+------------------+
2 rows in set (0.00 sec)

2. マスターポジション確認

そうしたら次はマスターの binlogファイル名とポジションを確認します。

MASTERステータスチェック
mysql> show master status;
+----------------------------+----------+--------------+------------------+-------------------+
| File                       | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+----------------------------+----------+--------------+------------------+-------------------+
| mysql-bin-changelog.000016 |      410 |              |                  |                   |
+----------------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

3. データベースdump

確認ができたらレプリケーションするデータベースのdumpを行います。

mysqldump
$ mysqldump -hrds001.cs8xkvdfl8ru.ap-northeast-1.rds.amazonaws.com --databases rds001 -uawsuser -pmypassword --single-transaction --routines > rds001.dump

4. リストア

dumpが完了したらスレーブ(EC2)へリストアします。

リストア
$ mysql -uroot -p < rds001.dump 

5. 読み取り位置の指定

リストアが完了したら先ほどのMASTERステータスで確認した値を元に、CHANGE MASTER TOでスレーブサーバーがマスターサーバーへ接続する際のバイナリログ読み取り位置を指定します。

mysql> change master to MASTER_HOST='rds001.cs8xkvdfl8ru.ap-northeast-1.rds.amazonaws.com', MASTER_USER='repl', MASTER_PASSWORD='hogehoge', MASTER_LOG_FILE='mysql-bin-changelog.000024', MASTER_LOG_POS=120;
Query OK, 0 rows affected (0.02 sec)

CHANGE MASTER TOでしたらSHOW SLAVE STATUSで設定内容を確認しておきましょう。

mysql> show slave statusG
*************************** 1. row ***************************
               Slave_IO_State: 
                  Master_Host: rds001.cs8xkvdfl8ru.ap-northeast-1.rds.amazonaws.com
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin-changelog.000024
          Read_Master_Log_Pos: 120
               Relay_Log_File: mysqld-relay-bin.000001
                Relay_Log_Pos: 4
        Relay_Master_Log_File: mysql-bin-changelog.000024
             Slave_IO_Running: No
            Slave_SQL_Running: No
              Replicate_Do_DB: 
          Replicate_Ignore_DB: information_schema,performance_schema,mysql
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 120
              Relay_Log_Space: 107
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 0
1 row in set (0.00 sec)

6. レプリケーションの開始

設定に問題なければSTART SLAVEでレプリケーションを開始します。

SLAVEスタート
mysql> start slave;
Query OK, 0 rows affected (0.00 sec)

スタートができたらステータスをもう一度確認してみます。

SLAVEステータスチェック
mysql> show slave statusG
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 10.0.2.150
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysql-bin-changelog.000024
          Read_Master_Log_Pos: 120
               Relay_Log_File: mysqld-relay-bin.000002
                Relay_Log_Pos: 276
        Relay_Master_Log_File: mysql-bin-changelog.000024
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
              Replicate_Do_DB: 
          Replicate_Ignore_DB: information_schema,performance_schema,mysql
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 0
                   Last_Error: 
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 120
              Relay_Log_Space: 433
              Until_Condition: None
               Until_Log_File: 
                Until_Log_Pos: 0
           Master_SSL_Allowed: No
           Master_SSL_CA_File: 
           Master_SSL_CA_Path: 
              Master_SSL_Cert: 
            Master_SSL_Cipher: 
               Master_SSL_Key: 
        Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 0
               Last_SQL_Error: 
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1505545156
1 row in set (0.00 sec)

Slave_IO_RunningSlave_SQL_RunningYes になっていれば完了です。

おわりに

RDSを使うのであればリードレプリカを利用するのが一番です。ですが、リードレプリカの料金も標準のインスタンスと同じなので、EC2と比べると費用がかかってしまいます。そういった場合にはこういう方法もありなのかもしれないです。
また、移行の際なども切り戻しを想定した場合はこの構成が必要ですね。

続きを読む