AWS Lambda(Node.js)からAmazon RDSへIAM認証で接続する

概要

現在開発しているburariというおでかけアプリでは、APIGatewayからLambdaを実行し、RDSにデータを取得している。
以前まで、LambdaからRDSへの安全な接続にはVPCが必須となっており、AWS Lambdaの定期実行をSAMを使って実装するにも書いたとおり、コールドスタート問題が懸念になっていたが、IAM認証でLambdaからRDSに接続できるようになったため、ついにVPCから解放される日がきた。VPCから解放されることにより、LambdaからSNSやKMSへの接続にNATが不要になるため、お財布にも優しいサービス運営が待ち受けている。
今回は接続まわりを実装してみる 💪💪

環境

  • Amazon RDS (MySQL v5.7.17, t2.micro)
  • AWS Lambda (Node.js v6.10)
  • Sequelize (ORM for Node.js)

制限

以下のような同時接続数への制限が発生するため、t2.microの場合は接続数を10以下に制限する必要がある。

IAM データベース認証を使用する場合は、db.t2.micro インスタンスクラスを使用している場合を除き、1 秒あたりの接続数を 20 以下に制限する必要があります。この場合、1 秒あたりの接続数を 10 以下に制限する必要があります。

RDSの「IAM DB Authentication Enabled」をオンにする

今回は既存のDBの設定をAWS Console上から変更する。

Screen Shot 2017-08-23 at 10.15.24 PM.png

変更するInstanceを選択し、Instance ActionsのModifyから、上記の「IAM DB Authentication Enabled」をYESにすればOK。

DB Userの作成

Masterユーザーでログインし、IAM認証用のユーザーを作成する。このときHostには「%」を指定。

CREATE USER 'iam_user'@'%' IDENTIFIED WITH AWSAuthenticationPlugin as 'RDS';

そしてSSL経由での使用を許可するよう変更。

GRANT SELECT,INSERT,UPDATE,DELETE ON dbname.* to 'iam_user'@'%' REQUIRE SSL;

IAM Policyの作成と、IAM Roleへの付与

以下のようなPolicyを作成する。dbi-resource-idはConsole上の「Resource ID」に記載されている。

AmazonRDSIAMAccess
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "rds-db:connect"
            ],
            "Resource": [
                "arn:aws:rds-db:ap-northeast-1:1234567890:dbuser:<dbi-resource-id>/iam_user"
            ]
        }
    ]
}

Policy作成後、Lambdaを実行するRoleに上記PolicyをAttachする。

コードの実装

Tokenを取得した後、Sequelizeを利用してRDSからデータを取得する。

index.js
import AWS from 'aws-sdk';
import Sequelize from 'sequelize';

export const handler = (event, context, callback) => {
    const connect = () => {
        return new Promise((resolve, reject) => {
            new AWS.RDS.Signer({
                'region': 'ap-northeast-1',
                'username': 'iam_user',
                'hostname': 'dbname.xxxxxxxx.ap-northeast-1.rds.amazonaws.com',
                'port': XXXX
            }).getAuthToken((error, token) => {
                if(error) {
                    reject(error);
                }

                resolve(token);
            });
        });
    };

    const fetch = (token) => {
        const options = {
            'host': 'dbname.xxxxxxxx.ap-northeast-1.rds.amazonaws.com',
            'port': XXXX,
            'ssl': true,
            'dialect': 'mysql',
            'dialectOptions': {
                'ssl': 'Amazon RDS',
                'authSwitchHandler': (data, callback) => {
                    if (data.pluginName === 'mysql_clear_password') {
                        const password = token + '';
                        const buffer = Buffer.from(password);
                        callback(null, buffer);
                    }
                }
            }
        };

        const sequelize = new Sequelize('dbname', 'iam_user', token, options);
        const model = sequelize.define('table_name', {
            'tableId': { 'type': Sequelize.STRING, 'primaryKey': true },
            'name': Sequelize.STRING
        });

        return model.findAll();
    };

    connect().then(fetch).then((data) => {
        context.succeed(data);

    }).catch((error) => {
        context.fail(error);
    });
}

実行結果

RDSからデータを取得することに成功 🙆🙆

Screen Shot 2017-08-24 at 1.28.28 AM.png

続きを読む

AWS Lambda(C#)からAuroraにつないでみる

Serverless Meetup Tokyo #4に参加してきたら

  • AWS SAから「ServerlessにはAuroraは不向き」的な話をされた
  • ENI作成時間が気になるなら、定期的にLambdaの暖機が必要

のような逆風にも負けずLambdaからAuroraに接続するアツい話を聞いて、無性にLambda(C#)からAuroraに接続したくなったので試してみました。

そう言えば「LambdaならC#よりnode.js/Pythonの方がオススメ」的な話も聞いたことがあるような…

開発環境

  • Mac
  • .Net Core 2.0.0 / .Net Core 1.0.5共存環境

本来Lambdaの作成には.Net Core 2.0.0は不要ですが
 ・自分の環境は1.0と2.0の共存環境で
 ・2.0.0になってdotnet restoreの振る舞いが変わった
ため、dotnetコマンドのバージョンが2.0.0の場合で記載します。

作業概要

  1. .Net Coreのインストール
  2. Auroraの作成
  3. C#コード作成
  4. デプロイ用パッケージの作成
  5. Lambdaの実行

1. .Net Coreのインストール

MicrosoftのサイトからMac用をダウンロードしてインストールします。

2.0.0
1.0.5 with SDK 1.0.4

2. Auroraの作成

AWSマネジメントコンソールからAuroraを構築します。

※留意点

  • VPC内からアクセスするため、Publicly Accessible:Noにします
  • お試しなら、インスタンスタイプは一番料金が安いdb.t2.smallがオススメです

3. C#コード作成

適当な作業ディレクトリを作成し、その配下に.csファイルを作成します。
またAuroraに接続するために、下記値を設定します。

  • クラスタエンドポイント
  • ユーザ / パスワード
  • データベース名
MyFunction.cs
using Amazon.Lambda.Core;
using MySql.Data.MySqlClient;

namespace sample_aurora
{
    public class MyFunction
    {
        const string ConnectionString = "<<クラスタエンドポイント>>;user=<<ユーザ>>;password=<<パスワード>>;port=3306;database=<<データベース名>>;SslMode=None";

        [LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]
        public string MyHandler(LambdaEvent lambdaEvent, ILambdaContext context)
        {
            string command = lambdaEvent.Command;
            LambdaLogger.Log("Command: " + command + "n");
            switch(command){
                case "select":
                    Select();
                    break;
                case "insert":
                    Insert();
                    break;
                case "init":
                    Init();
                    break;
                default:
                    LambdaLogger.Log("Do nothing.n");
                    break;

            }
            return "Sample function was executed.";
        }

        private void Select()
        {
            MySqlConnection connection = new MySqlConnection(ConnectionString);
            connection.Open();

            MySqlCommand command = new MySqlCommand("select id from test_tbl;", connection);

            string message = "Data: ";

            using (MySqlDataReader reader = command.ExecuteReader())
            {
                while (reader.Read())
                {
                    string row = $"{reader["id"]}";
                    message = message + "," + row;
                }
            }

            connection.Close();

            LambdaLogger.Log(message + "n");
        }

        private void Insert()
        {
            MySqlConnection connection = new MySqlConnection(ConnectionString);
            connection.Open();

            MySqlCommand command = new MySqlCommand("insert into test_tbl values ();", connection);

            command.ExecuteNonQuery();

            connection.Close();

            LambdaLogger.Log("A record has been inserted.n");
        }

        private void Init()
        {
            MySqlConnection connection = new MySqlConnection(ConnectionString);
            connection.Open();

            MySqlCommand command = new MySqlCommand("create table if not exists test_tbl (id int auto_increment primary key);", connection);

            command.ExecuteNonQuery();

            connection.Close();

            LambdaLogger.Log("A table has been created.n");
        }
    }

    public class LambdaEvent
    {
        public string Command { get; set; }
    }
}

4. デプロイ用パッケージの作成

4-1. .csprojファイルの作成

同じ作業ディレクトリに.csprojファイルを作成します。

sample_aurora.csproj
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp1.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Amazon.Lambda.TestUtilities" Version="1.0.0" />
    <PackageReference Include="Amazon.Lambda.Serialization.Json" Version="1.1.0" />
    <PackageReference Include="Amazon.Lambda.Core" Version="1.0.0" />
    <PackageReference Include="MySql.Data.Core" Version="7.0.4-IR-191" />
  </ItemGroup>
</Project>

4-2. デプロイパッケージの作成

作業ディレクトリ内で以下コマンドを実行します。

$ dotnet restore
$ dotnet publish --output publish
$ cp ~/.nuget/packages/system.data.sqlclient/4.1.0/runtimes/unix/lib/netstandard1.3/System.Data.SqlClient.dll publish/
$ cp ~/.nuget/packages/system.diagnostics.tracesource/4.0.0/runtimes/unix/lib/netstandard1.3/System.Diagnostics.TraceSource.dll pulish/
$ cd publish
$ zip ../sample_aurora.zip *

※留意点

  • System.Data.SqlClient.dllとSystem.Diagnostics.TraceSource.dllはdotnet publishでは含まれなかったため、手動でコピーしています。(無いとLambda実行時にエラーになります)

4-3. Lamdbaのデプロイ

AWSマネジメントコンソールからLambdaを選んで、「sample_aurora.zip」をデプロイします。

  • VPC対応にします
  • Auroraに接続できるsecurity groupを指定します
  • もしくは、指定したsubnet groupがaurora側のsecurity groupで許可されていれば、lamdbaで指定するsecurity grouopはダミーで構いません

5. Lambdaの実行

AWSマネジメントコンソールからLambdaを実行します。

5-1. テーブル作成

test eventに以下のjsonを指定してLambdaを実行します。

{
  "Command": "init"
}

Log outputに以下出力されます。

Command: init
A table has been created.

5-2. Insert

test eventに以下のjsonを指定してLambdaを実行します。

{
  "Command": "insert"
}

Log outputに以下出力されます。

Command: insert
A record has been inserted.

実行した回数分テーブルにレコードが追加されます。

5-3. Select

test eventに以下のjsonを指定してLambdaを実行します。

{
  "Command": "select"
}

Log outputに以下出力されます。

Command: select
Data: ,1,2,3

雑感

案外簡単にC# LambdaからAuroraへ接続できました。

続きを読む

AWSのec2インスタンスを別アカウントのec2で立ち上げる

お客さんから、「EC2でシクヨロ」って言われて自分の環境にEC2立ち上げたら
「あ、AWS契約したからこっちで契約したAWS上でシクヨロ!」
って言われることよくありますよね。いや、ない。

そんな時でも大丈夫。簡単に移設できます。そう、AWSならね。

EC2をまず立ち上げましょう

立ち上げます。
多分PHP+Nginx+php-fpm+mysql+WP+laravelとかですよね。
そのうち、↑をAnsibleで一発で立ち上げるやつ書きます。

お客さんにAWS契約したからこっちでシクヨロ!って言われる

はい、がっかりします。
泣きながらお客さんのAWS環境にログインしましょう。
もちろん、EC2のアクセス権限はもらいましょう。

さっき立ち上げたEC2をAMI化しましょう。

AMI化手順は以下の通り。
移設したいEC2を選択して、

  • アクション>イメージ>イメージの作成

を選択。以下図。

1.png

なんか血しぶきみたいになっちまった:scream:

そして、作成したらこんな画面になるので適当にわかりやすい名前を付けます。

2.PNG

麦茶を飲んで待ってれば出来上がります。
出来たかどうかは左メニューからAMIを選んでみましょう

3.png

はい、順調に作成されていますね。

さあ共有。

ん?見慣れない文字、AWSアカウント番号??

4.png

AWSアカウント番号とは!?

あわてない、あわてない。一休み、一休み。
右上のサポート>サポートセンターを選択するとなんと!見ることが出来ます。
移行先のアカウント番号を入力して、イメージパーミッションの変更は終了です。

5.png

移行先のEC2管理画面を開いてみよう

EC2 > イメージ > AMI を選択してみて下さい。
移行先のEC2管理画面を見てみると、はいありました。
あとはこれを選択して上のほうの作成ボタンを押すとちょっと待てば出来上がり!

この記事で絶望の淵にいた人を一人でも救えるとうれしいな。

続きを読む

現在のインスタンス料金を取得する script: RDS 編

http://qiita.com/bells17/items/5326d11edc6acc4feea2
の RDS 版です

注意点として取得するデータのエンジンを Aurora に絞ってます

rds.rb
require 'json'
require 'bigdecimal'

results = {}
json_data = open(ARGV[0]) {|io| JSON.load(io) }

# product 情報を取得
json_data['products'].keys.each do |skuNo|
    product = json_data['products'][skuNo]

    if (product['productFamily'] == 'Database Instance' and
          product['attributes']['locationType'] == 'AWS Region' and
          product['attributes']['location'] == 'Asia Pacific (Tokyo)' and
          product['attributes']['databaseEngine'] == 'Amazon Aurora') # Aurora だけに絞ってます (エンジンが mysql か postgresql かは無いっぽい??)

        results[product['sku']] = {
            sku: product['sku'],
            location: product['attributes']['location'],
            instanceType: product['attributes']['instanceType'],
            instanceFamily: product['attributes']['instanceFamily'],
            vcpu: product['attributes']['vcpu'],
            physicalProcessor: product['attributes']['physicalProcessor'],
            clockSpeed: product['attributes']['clockSpeed'],
            memory: product['attributes']['memory'],
            networkPerformance: product['attributes']['networkPerformance'],
            currentGeneration: product['attributes']['currentGeneration'],
            price_unit: 'USD'
        }

    end
end


# price

# on demand
json_data['terms']['OnDemand'].keys.each do |skuNo|
    if (results[skuNo])
        results[skuNo][:price_per_hour] = Proc.new {
            skuTerm = json_data['terms']['OnDemand'][skuNo][json_data['terms']['OnDemand'][skuNo].keys[0]]
            priceInfo = skuTerm['priceDimensions'][skuTerm['priceDimensions'].keys[0]]
            BigDecimal(priceInfo['pricePerUnit']['USD']).floor(2).to_f.to_s
        }.call
        results[skuNo][:price_per_day] = (BigDecimal(results[skuNo][:price_per_hour]) * BigDecimal("24")).floor(2).to_f.to_s
        results[skuNo][:price_per_month] = (BigDecimal(results[skuNo][:price_per_day]) * BigDecimal("30")).floor(2).to_f.to_s
    end
end

## reserved 
json_data['terms']['Reserved'].keys.each do |skuNo|
    if (results[skuNo])

        plans = json_data['terms']['Reserved'][skuNo].values.select do |plan|
            plan['termAttributes']['PurchaseOption'] == "All Upfront" # "All Upfront" のものだけ取得したい
        end

        results[skuNo][:price_reserved_1year_purchased_all_upfront] = plans.find { |plan|
            plan['termAttributes']['LeaseContractLength'] == '1yr'
        }['priceDimensions'].values.find {|priceDimension|
            priceDimension['description'] == "Upfront Fee"
        }['pricePerUnit']['USD']

        results[skuNo][:price_reserved_3year_purchased_all_upfront] = plans.find { |plan|
            plan['termAttributes']['LeaseContractLength'] == '3yr'
        }['priceDimensions'].values.find {|priceDimension|
            priceDimension['description'] == "Upfront Fee"
        }['pricePerUnit']['USD']

    end
end

# sort
sorted_result = {}
results.values.each do |row|
    sorted_result[row[:currentGeneration]] ||= {}
    sorted_result[row[:currentGeneration]][row[:instanceFamily]] ||= []
    sorted_result[row[:currentGeneration]][row[:instanceFamily]].push row
end

results = []
['Yes', 'No'].each do |currentGeneration| # 現行世代のものから並べる
    next unless sorted_result[currentGeneration]
    sorted_result[currentGeneration].keys.sort.each do |instanceFamily| # インスタンスファミリー毎に並べる
        results.concat sorted_result[currentGeneration][instanceFamily].sort_by { |row| row[:price_per_hour] }
    end
end

p results.to_json

上記を保存して以下のように実行する

curl https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonRDS/current/index.json > price-AmazonRDS.json
ruby rds.rb price-AmazonRDS.json | sed -e s/^"// | sed -e s/"$// | sed -e 's/\"/"/g' | jq .

以下のような結果が取れる

[
  {
    "sku": "H7JQN46Z6VDZ3K5V",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.t2.small",
    "instanceFamily": "General purpose",
    "vcpu": "1",
    "physicalProcessor": "Intel Xeon Family",
    "clockSpeed": "Up to 3.3 GHz",
    "memory": "2 GiB",
    "networkPerformance": "Low to Moderate",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "0.06",
    "price_per_day": "1.44",
    "price_per_month": "43.2",
    "price_reserved_1year_purchased_all_upfront": "403",
    "price_reserved_3year_purchased_all_upfront": "776"
  },
  {
    "sku": "MK8ETWDCPSK52PEV",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.t2.medium",
    "instanceFamily": "General purpose",
    "vcpu": "2",
    "physicalProcessor": "Intel Xeon Family",
    "clockSpeed": "Up to 3.3 GHz",
    "memory": "4 GiB",
    "networkPerformance": "Low to Moderate",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "0.12",
    "price_per_day": "2.88",
    "price_per_month": "86.4",
    "price_reserved_1year_purchased_all_upfront": "792",
    "price_reserved_3year_purchased_all_upfront": "1530"
  },
  {
    "sku": "8Z6GS5F6NKX37Q5E",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.r3.large",
    "instanceFamily": "Memory optimized",
    "vcpu": "2",
    "physicalProcessor": "Intel Xeon E5-2670 v2 (Ivy Bridge)",
    "clockSpeed": "2.5 GHz",
    "memory": "15.25 GiB",
    "networkPerformance": "Moderate",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "0.35",
    "price_per_day": "8.4",
    "price_per_month": "252.0",
    "price_reserved_1year_purchased_all_upfront": "1704",
    "price_reserved_3year_purchased_all_upfront": "3433"
  },
  {
    "sku": "PQP78BGE4C2HXDQF",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.r3.xlarge",
    "instanceFamily": "Memory optimized",
    "vcpu": "4",
    "physicalProcessor": "Intel Xeon E5-2670 v2 (Ivy Bridge)",
    "clockSpeed": "2.5 GHz",
    "memory": "30.5 GiB",
    "networkPerformance": "Moderate",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "0.7",
    "price_per_day": "16.8",
    "price_per_month": "504.0",
    "price_reserved_1year_purchased_all_upfront": "3408",
    "price_reserved_3year_purchased_all_upfront": "6867"
  },
  {
    "sku": "2WTMTR9HDDT7AA73",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.r3.2xlarge",
    "instanceFamily": "Memory optimized",
    "vcpu": "8",
    "physicalProcessor": "Intel Xeon E5-2670 v2 (Ivy Bridge)",
    "clockSpeed": "2.5 GHz",
    "memory": "61 GiB",
    "networkPerformance": "High",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "1.4",
    "price_per_day": "33.6",
    "price_per_month": "1008.0",
    "price_reserved_1year_purchased_all_upfront": "6815",
    "price_reserved_3year_purchased_all_upfront": "13733"
  },
  {
    "sku": "VRNJP9SPPRH2KM8M",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.r3.4xlarge",
    "instanceFamily": "Memory optimized",
    "vcpu": "16",
    "physicalProcessor": "Intel Xeon E5-2670 v2 (Ivy Bridge)",
    "clockSpeed": "2.5 GHz",
    "memory": "122 GiB",
    "networkPerformance": "High",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "2.8",
    "price_per_day": "67.2",
    "price_per_month": "2016.0",
    "price_reserved_1year_purchased_all_upfront": "13631",
    "price_reserved_3year_purchased_all_upfront": "27466"
  },
  {
    "sku": "NC3BZ293ZJFBVUT5",
    "location": "Asia Pacific (Tokyo)",
    "instanceType": "db.r3.8xlarge",
    "instanceFamily": "Memory optimized",
    "vcpu": "32",
    "physicalProcessor": "Intel Xeon E5-2670 v2 (Ivy Bridge)",
    "clockSpeed": "2.5 GHz",
    "memory": "244 GiB",
    "networkPerformance": "10 Gigabit",
    "currentGeneration": "Yes",
    "price_unit": "USD",
    "price_per_hour": "5.6",
    "price_per_day": "134.4",
    "price_per_month": "4032.0",
    "price_reserved_1year_purchased_all_upfront": "27261",
    "price_reserved_3year_purchased_all_upfront": "54932"
  }
]

続きを読む

[2017夏版] AWS Start-up ゼミ参考資料リンク集をマークダウンに起こしました

TL;DR

こんにちは。AWSリハビリ中のnntsuguです。

[AWS Start-up ゼミ] よくある課題を一気に解説!〜御社の技術レベルがアップする 2017 夏期講習〜

先日参加させていただいたAWS Stat-upゼミ、講師のSA塚田さんの資料がとても良かった。
AWS上でサービスを構築運用する上での勘所がユースケースベースで整理されていて、モヤモヤしていた部分がかなりスッキリししました。

資料内にある参考資料や動画へのLink、とても勉強になるのですが、

  • PDFやSlideShareだとスマホから参照しづらい
  • 未読管理をしやすくするため

マークアップに起こしました。

毎朝ジムで走りながら参考資料の動画を見て聞いています。とても捗ります。

参考資料は主にBlack Beltの資料&動画アーカイブ、AWS Summit/Dev Dayの資料で構成されています。

ユーザ動向を分析したい

CI/CDをちゃんとしたい

コンテナを使いたい

運用監視ちゃんとしたい

システム負荷下げたい

(モバイルアプリの)Growth Hackしたい

コスト下げたい

  • AWS Black Belt Online Seminar資料&動画

    • クラウドのためのアーキテクチャ設計-ベストプラクティス-(資料|動画)
    • Auto Scaling (資料 | 動画)
    • Amazon EC2 Spot Instances (資料 | 動画)
    • サーバーレスによるアーキテクチャパターンのご紹介 (資料 | 動画)
  • AWS Summit/Dev Day講演資料 (2016 | 2017)
    • AWS のコスト最適化入門 (2017)(資料 | 動画)
    • [インティメート・マージャー様] AWS Summit 2017 講演資料 Amazon ECS と SpotFleet を活用した低コストでスケーラブルなジョ
      ブワーカーシステム(資料 | 動画)
    • AWS Well-Architected フレームワークによるクラウド ベスト プラク
      ティス (2017) (資料 | 動画)

その他

IPOとBuy Out、デューデリジェンス

続きを読む

AWS EC2 Mysql レプリケーション環境構築

はじめに

本番環境はRDSのMulti A-Z でレプリケーションしているのですが、selectはslaveに、FOR UPDATEはmasterになどクエリの向き先をしっかり確認しておく必要があったので勉強がてらEC2でレプリケーション環境を構築したので自分用にメモ。

やること

・ インスタンスを2つ起動
・ レプリケーション設定
・ 接続確認

EC2インスタンス起動

セキュリティ設定
2つのサーバのMysqlが互いに通信できるように、
Inboundで3306ポートに対して、互いのPrivateIPを設定します。

レプリケーション設定

Master-Server

my.conf

/etc/my.cnf
[mysqld]
server-id=101
log_bin

server-idは、1から(2^32)-1)間の正整数で、サーバ間で重複しないように設定する(任意の値でOK)
ただし、1、2はserver-idがないときのデフォルトとして使用されるため極力使わないほうがいい

Slave-ServerのUser作成とアクセスの許可

mysql > grant replication slave on *.* to 'replication'@'Slave-Srever Private IP' identified by 'password'

ステータスの確認

mysql > show master status;
+-------------------+----------+--------------+------------------+
| File              | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+-------------------+----------+--------------+------------------+
| mysqld-bin.000001 |     1234 |              |                  |
+-------------------+----------+--------------+------------------+

Slave-Server

my.conf

/etc/my.cnf
[mysqld]
server-id=201
replicate-do-db=db名(対象DB指定)

Master-Server登録

mysql > change master to master_host = 'Master-Server Private IP',
        master_user = 'replication',
        master_password = 'pass',
        master_log_file = 'mysqld-bin.000001', //Master-Serverの情報
        master_log_pos = 1234 //Master-Serverの情報

Slave-ServerのUser作成とアクセスの許可

mysql > grant all privileges on *.* to name@'Master-Srever Private IP' identified by 'password';

ステータスの確認

mysql > show slave status \G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: Master-Server Private IP
                  Master_User: replication
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: mysqld-bin.000001
          Read_Master_Log_Pos: 2258
               Relay_Log_File: mysqld-relay-bin.000004
                Relay_Log_Pos: 10195
        Relay_Master_Log_File: mysqld-bin.000001
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes

Slave_IO_Running: Yes、Slave_SQL_Running: Yesで設定OK

接続確認

各サーバーからホスト指定でログインできれば接続はできている状態となります。
下記のエラーが出る場合はEC2インスタンスのセキュリティを見直す必要があります。

ERROR 1130 (HY000): Host 'Private IP' is not allowed to connect to this MySQL server

さいごに

その都度、各mysqlの再起動や必要であれば権限の反映FLUSH PRIVILEGESが必要になるかと思います。
自分用のメモとして記述しましたが、不備がありましたらご教授いただけますと幸いです。

続きを読む

AWS + Nginx + PHP + Laravel

nginx + php + LaravelをAWS上に構築してみる

nginx

  • インストールと起動
$ sudo yum -y install nginx
・・・・・
完了しました!

$ sudo service nginx start
Starting nginx:                                            [  OK  ]
  • バージョンやconfigurationの内容を知りたいときは下記コマンド
$ nginx -V
  • configurationで使いそうなやつメモ
設定 説明 デフォルト
–error-log-path HTTPアクセスログのエラーのパス /var/log/nginx/error.log
–http-log-path HTTPアクセスログのパス /var/log/nginx/access.log
–conf-path nginxの設定ファイルのパス /etc/nginx/nginx.conf
–http-proxy-temp-path プロキシを実行している場合、ここで指定したディレクトリが一時ファイルの格納パスになる /var/lib/nginx/tmp/proxy
  • モジュールで気になるところメモあたり(他にもあったけど、メモるの面倒でdown)
モジュール名 説明 利用場面 デフォルト
http_ssl https対応(OpenSSLライブラリが必要)。 プロキシ 有効
http_realip L7ロードバランサなどの後に配置する場合有効にする必要あり。複数のクライアントが同一IPアドレスから通信してくるように見える環境で使用。 プロキシ 有効
http_geoip クライアントのIPアドレスに基づく地理的位置に応じた処理を行うための様々な変数を設定 Web、プロキシ 有効
http_stub_status Nginx自身の統計情報の収集を手助けする Web、プロキシ 有効

※有効化(–with-<モジュール名>module)、無効化(–without-<モジュール名>module)

PHP7のインストール

  • CentOS6用のPHP7のリポジトリを追加(これがないとインストールできないくさい)
$ sudo yum install --enablerepo=webtatic-testing 
                 php70w php70w-devel php70w-fpm php70w-mysql 
                 php70w-mbstring php70w-pdo
  • 他にも必要であればインストールしておく(json系とか)

nginxとphpの紐付け

  • index.phpのセット

    • /var/www/default ディレクトリ作成
    • ここにindex.phpを配置 (最初はとりあえずphpinfoを吐くだけ)
  • /etc/php-fpm.d/www.confの編集 (backupを取った上で編集)
$ diff -uN www.conf.backup_20160710 www.conf
--- www.conf.backup_20160710    2016-07-10 08:00:45.267704077 +0000
+++ www.conf    2016-07-10 08:01:38.451085053 +0000
@@ -5,9 +5,11 @@
 ; Note: The user is mandatory. If the group is not set, the default user's group
 ;       will be used.
 ; RPM: apache Choosed to be able to access some dir as httpd
-user = apache
+; user = apache
+user = nginx
 ; RPM: Keep a group allowed to write in log dir.
-group = apache
+; group = apache
+group = nginx
  • /etc/nginx/nginx.confの編集 (backupを取った上で編集)
$ diff -uN nginx.conf.backup_20160710 nginx.conf
--- nginx.conf.backup_20160710  2016-07-10 07:49:38.694839828 +0000
+++ nginx.conf  2016-07-10 07:59:49.564346085 +0000
@@ -32,13 +32,14 @@
     # for more information.
     include /etc/nginx/conf.d/*.conf;

-    index   index.html index.htm;
+    index   index.php index.html index.htm;

     server {
         listen       80 default_server;
         listen       [::]:80 default_server;
         server_name  localhost;
-        root         /usr/share/nginx/html;
+        #root         /usr/share/nginx/html;
+        root         /var/www/default;

         # Load configuration files for the default server block.
         include /etc/nginx/default.d/*.conf;
@@ -46,8 +47,17 @@
         location / {
         }

-        # redirect server error pages to the static page /40x.html
+        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
         #
+        location ~ .php$ {
+            root           /var/www/default;
+            fastcgi_pass   127.0.0.1:9000;
+            fastcgi_index  index.php;
+            fastcgi_param  SCRIPT_FILENAME  /var/www/default$fastcgi_script_name;
+            include        fastcgi_params;
+        }
+
+        # redirect server error pages to the static page /40x.html
         error_page 404 /404.html;
             location = /40x.html {
         }
@@ -64,16 +74,6 @@
         #    proxy_pass   http://127.0.0.1;
         #}

-        # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
-        #
-        #location ~ .php$ {
-        #    root           html;
-        #    fastcgi_pass   127.0.0.1:9000;
-        #    fastcgi_index  index.php;
-        #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
-        #    include        fastcgi_params;
-        #}
-
  • 再起動して、phpinfoページが見れればOK (http://<>)
$ sudo service php-fpm start
Starting php-fpm:                                          [  OK  ]
$ sudo service nginx restart
Stopping nginx:                                            [  OK  ]
Starting nginx:                                            [  OK  ]
  • ついでにサーバ起動時などに自動で起動するものも設定
$ sudo chkconfig nginx on
$ sudo chkconfig php-fpm on

nginxとphp-fpmの接続をsocketにする

  • php-fpmの設定変更
$ diff -uN www.conf.backup_20160710 www.conf
--- www.conf.backup_20160710    2016-07-10 08:00:45.267704077 +0000
+++ www.conf    2016-07-10 08:19:03.630366042 +0000
@@ -19,7 +21,8 @@
 ;                            (IPv6 and IPv4-mapped) on a specific port;
 ;   '/path/to/unix/socket' - to listen on a unix socket.
 ; Note: This value is mandatory.
-listen = 127.0.0.1:9000
+; listen = 127.0.0.1:9000
+listen = /var/run/php-fpm/php-fpm.sock

@@ -32,6 +35,8 @@
 ;                 mode is set to 0660
 ;listen.owner = nobody
 ;listen.group = nobody
+listen.owner = nginx
+listen.group = nginx
 ;listen.mode = 0660
  • nginxの設定変更
$ diff -uN nginx.conf.backup_20160710 nginx.conf
--- nginx.conf.backup_20160710  2016-07-10 07:49:38.694839828 +0000
+++ nginx.conf  2016-07-10 08:20:37.741301066 +0000
@@ -46,8 +47,17 @@
-            fastcgi_pass   127.0.0.1:9000;
+            fastcgi_pass   unix:/var/run/php-fpm/php-fpm.sock;
  • 再起動
$ sudo service php-fpm restart
Stopping php-fpm:                                          [  OK  ]
Starting php-fpm:                                          [  OK  ]
$ sudo service nginx restart
Stopping nginx:                                            [  OK  ]
Starting nginx:                                            [  OK  ]

Laravel5を入れてみる

  • Composerをインストール
$ curl -sS https://getcomposer.org/installer | php
$ sudo mv /home/ec2-user/composer.phar /usr/local/bin/composer
  • Laravelのインストール
$ sudo /usr/local/bin/composer global require "laravel/installer"
Changed current directory to /root/.composer
Using version ^1.3 for laravel/installer
./composer.json has been created
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing symfony/process (v3.1.2)
    Downloading: 100%         

  - Installing symfony/polyfill-mbstring (v1.2.0)
    Downloading: 100%         

  - Installing symfony/console (v3.1.2)
    Downloading: 100%         

  - Installing guzzlehttp/promises (1.2.0)
    Downloading: 100%         

  - Installing psr/http-message (1.0)
    Downloading: 100%         

  - Installing guzzlehttp/psr7 (1.3.1)
    Downloading: 100%         

  - Installing guzzlehttp/guzzle (6.2.0)
    Downloading: 100%         

  - Installing laravel/installer (v1.3.3)
    Downloading: 100%         

symfony/console suggests installing symfony/event-dispatcher ()
symfony/console suggests installing psr/log (For using the console logger)
Writing lock file
Generating autoload files
  • php-xmlのインストール (laravelで必要になる)
$ sudo yum install --enablerepo=webtatic-testing php70w-xml
  • プロジェクト作成
$ pwd
/var/www/default
$ sudo /usr/local/bin/composer create-project --prefer-dist laravel/laravel darmaso
Installing laravel/laravel (v5.2.31)
  - Installing laravel/laravel (v5.2.31)
    Downloading: 100%         

Created project in darmaso
> php -r "copy('.env.example', '.env');"
Loading composer repositories with package information
Updating dependencies (including require-dev)
・・・・・ (下記の結果と同じ)

$ cd darmaso
$ sudo /usr/local/bin/composer install
Loading composer repositories with package information
Updating dependencies (including require-dev)
・・・・・
Writing lock file
Generating autoload files
> IlluminateFoundationComposerScripts::postUpdate
> php artisan optimize
Generating optimized class loader

※php-xmlをインストールしておかないと、下記のようなエラーが出るので注意
Your requirements could not be resolved to an installable set of packages.

  Problem 1
    - phpunit/phpunit 4.8.9 requires ext-dom * -> the requested PHP extension dom is missing from your system.
・・・・・
    - Installation request for phpunit/phpunit ~4.0 -> satisfiable by phpunit/phpunit[4.0.0, 4.0.1, 4.0.10, 4.0.11, 4.0.12, 4.0.13, 4.0.14, 4.0.15, 4.0.16, 4.0.17, 4.0.18, 4.0.19, 4.0.2, 4.0.20, 〜
・・・・・
  To enable extensions, verify that they are enabled in those .ini files:
    - /etc/php.ini
    - /etc/php.d/bz2.ini
    - /etc/php.d/calendar.ini
・・・・・
  You can also run `php --ini` inside terminal to see which files are used by PHP in CLI mode.
  • Applicationキーの生成 (composerでインストールした場合セットされているらしいが念のため)
$ sudo php artisan key:generate
Application key [base64:YVeCf2A+5IjUbk2qVL4HhPiecBdYuo8irJrEYjJKZWY=] set successfully.
  • Laravel用にnginx設定を修正し、再起動
$ diff -uN nginx.conf.backup_20160710 nginx.conf
+        #root         /var/www/default;
+        root         /var/www/default/darmaso/public;
・・・・・
         location / {
+            try_files $uri $uri/ /index.php?$query_string;
         }
・・・・・
+            #root           /var/www/default;
+            root           /var/www/default/darmaso/public;
・・・・・
+            #fastcgi_param  SCRIPT_FILENAME  /var/www/default$fastcgi_script_name;
+            fastcgi_param  SCRIPT_FILENAME  /var/www/default/darmaso/public$fastcgi_script_name;

$ sudo service php-fpm restart
$ sudo service nginx restart
  • これで動作確認するとエラーになるので下記の設定をしてみる
$ sudo chmod -R 777 storage/
$ sudo chmod -R 777 vendor/

※本来は、サーバアカウントをちゃんと定義してやるべきだが、今回は試しなのでこのままでOKとする

  • 一部の設定を変えてみる
config/app.php
$ diff -uN config/app.php.backup_20160710 config/app.php
--- config/app.php.backup_20160710  2016-07-10 09:37:07.881735079 +0000
+++ config/app.php  2016-07-10 09:40:54.263419145 +0000
@@ -52,7 +52,7 @@
     |
     */

-    'timezone' => 'UTC',
+    'timezone' => 'Asia/Tokyo',

     /*
     |--------------------------------------------------------------------------
@@ -65,7 +65,7 @@
     |
     */

-    'locale' => 'en',
+    'locale' => 'jp',

     /*
     |--------------------------------------------------------------------------
@@ -78,7 +78,7 @@
     |
     */

-    'fallback_locale' => 'en',
+    'fallback_locale' => 'jp',

これで構築した環境にアクセスしたところ、無事いけました!
設定内容が荒いところもありますが、上記まででPHP+Nginx自体はいけちゃいますね。

Nginxの設定はあまり大したことはできませんでしたが、今後は色々と勉強してみようと思いますmm

参考

続きを読む

MySQLが統計情報を計算するタイミングで再起動してしまう

対象バージョン

MySQL 5.6.35, 5.7.17

現象

AWS より RDS 上の MySQL のうち 5.6.19a, 5.6.19b, 5.6.21, 5.6.21b, 5.6.22, and 5.6.23 の各バージョンが廃止となるという連絡があり、その時点での最新バージョンである 5.6.35 にアップデートを実施した。
するとバージョンアップ後しばらくして以下のエラーが発生し、断続的に再起動を繰り返すようになってしまった。
(ログは一部改変しています)

terminate called after throwing an instance of 'std::out_of_range'
what(): vector::_M_range_check
06:01:31 UTC - mysqld got signal 6 ;
This could be because you hit a bug. It is also possible that this binary
or one of the libraries it was linked against is corrupt, improperly built,
or misconfigured. This error can also be caused by malfunctioning hardware.
We will try our best to scrape up some info that will hopefully help
diagnose the problem, but since we have already crashed, 
something is definitely wrong and this may fail.
key_buffer_size=xxxxxxxx
read_buffer_size=xxxxxxxx
max_used_connections=xxxx
max_threads=xxxx
thread_count=xxxx
connection_count=xxxx
It is possible that mysqld could use up to 
key_buffer_size + (read_buffer_size + sort_buffer_size)*max_threads = xxxxxx K bytes of memory
Hope that's ok; if not, decrease some variables in the equation.
Thread pointer: 0x0
Attempting backtrace. You can use the following information to find out
where mysqld died. If you see no messages after this, something went
terribly wrong...
stack_bottom = 0 thread_stack 0x40000
/rdsdbbin/mysql/bin/mysqld(my_print_stacktrace+0x33)[0x876d73]
/rdsdbbin/mysql/bin/mysqld(handle_fatal_signal+0x35c)[0x64089c]
/lib64/libpthread.so.0(+0x10f90)[0x7fecc63d2f90]
/lib64/libc.so.6(gsignal+0x37)[0x7fecc57e8167]
/lib64/libc.so.6(abort+0x16a)[0x7fecc57e95ea]
/usr/lib64/libstdc++.so.6(_ZN9__gnu_cxx27__verbose_terminate_handlerEv+0x16d)[0x7fecc60e017d]
/usr/lib64/libstdc++.so.6(+0x73fc6)[0x7fecc60ddfc6]
/usr/lib64/libstdc++.so.6(+0x74011)[0x7fecc60de011]
/usr/lib64/libstdc++.so.6(+0x74228)[0x7fecc60de228]
/usr/lib64/libstdc++.so.6(_ZSt20__throw_out_of_rangePKc+0x139)[0x7fecc613e869]
/rdsdbbin/mysql/bin/mysqld[0xa8e394]
/rdsdbbin/mysql/bin/mysqld[0xa8fb59]
/rdsdbbin/mysql/bin/mysqld[0xa90ec0]
/lib64/libpthread.so.0(+0x7f18)[0x2ada4e15bf18]
/lib64/libc.so.6(clone+0x6d)[0x2ada4f2a5b2d]
The manual page at http://dev.mysql.com/doc/mysql/en/crashing.html contains
information that should help you find out what is causing the crash.
…

原因

以下 URL にある通り、MySQL の 5.6.35, 5.7.17 でのみ発生するバグとのこと。
https://bugs.mysql.com/bug.php?id=84940

対応

以下2つの対応方法がある。

1. 問題のあるバージョンを回避する

次のバージョンで FIX されているため、5.6.36, 5.7.18 以降のバージョンにアップデートすればよい。

ただ、RDS の場合はちょっと厄介で、2017/8/14 の時点では 5.7.18 以降を選択できないため、5.7.17 になっている場合はダウングレードが必要となってしまう。
(問題の発生した環境は運良く 5.6.35 だったので、5.7.16 にバージョンアップをすることで回避できた)

RDS は現在より前のバージョンを選択できず、スナップショットからの復元でもバージョンは選択できないので、新たにインスタンスを立ち上げて、そこにデータを流し込む方法になると思われる。

2. インデックス統計を永続化しない

前述の URL によると、削除行のインデックス統計判定の不具合(※MariaDBでの修正箇所です)が原因とのこと。
なので、クエリパラメータより以下の設定をすればよいらしい。

[mysqld]
innodb-stats-persistent             = 0
innodb-stats-transient-sample-pages = 20
innodb-stats-auto-recalc            = 0

ただし、この変更によりインデックス統計が非永続的になり、性能劣化が予測されるため、今回は選択しなかった。

備考

MySQL 5.6 と 5.7 の互換性に注意が必要である。
ハマったところで言うと、以下の2つ。

  • sql_mode の ONLY_FULL_GROUP_BY,NO_ZERO_IN_DATE,NO_ZERO_DATE がデフォルトで ON
  • ROW_FORMAT=FIXED が廃止

RDS の場合は新しいパラメータグループを作成するが、複数のパラメータグループを比較できるので、それを利用すれば分かりやすいかもしれない。

スクリーンショット 2017-08-15 11.40.15.png

character_set_server を latin1 から utf8mb4 にしなければいけないのもここで気づくことができた。

続きを読む

Rails5アプリケーションのAWSによるネットワーク構築 Nginx+Puma+Capistranoな環境とAWS構築(VPC EC2 RDS CloudFlont Route53 etc)

はじめに

数ヶ月前に書いたRails5のAWS構築をもう少し項目を増やしてから公開しようと思っていたのですが、なかなか出来ないのでもう公開します。(ELBとかCloudFlontとかもっとちゃんと書けそう、、)
項目の追加や修正は適宜入れると思います。

Railsは5.0を使っています。(記事を書いたときの最新)

4系は以前書きました↓

構築する環境

  • VPC
  • EC2
    • Nginx + Puma
    • Capistrano
  • (S3)
  • ELB
  • RDS
  • CloudFlont
  • Route53
  • (CloudWatch)

VPC、EC2のインスタンスの作成

AWSのデザインが多少変更はありますが、以下を参考に作成出来ます。

AWS VPCによるネットワーク構築とEC2によるサーバー構築

これに追加でElastic IPの設定もしておきました。

EC2内の環境構築

作成したEC2にログインして環境を構築します。
ec2-userでまずログインしましょう。

ユーザーの作成

デフォルトではec2-userなので新たなユーザーを追加し、そのユーザーでsshログイン出来るように設定します。

$ sudo adduser shizuma #ユーザーの追加 「shizuma」の部分は好きなユーザー名にします。以後「shizuma」の部分は各自のユーザー名になります。
$ sudo passwd shizuma
#ここで、新規ユーザーのパスワードを設定します。
$ sudo visudo
-----------------------------
#vimが起動するので新規ユーザーにroot権限を与える。
root    ALL=(ALL)       ALL
shizuma ALL=(ALL)       ALL #この行を追加
-----------------------------
$ sudo su - shizuma #ユーザー切り替え
#先ほど設定したパスワード

ここでローカルに一旦戻り、鍵を作成します。

$ cd .ssh
$ ssh-keygen -t rsa
-----------------------------
Enter file in which to save the key ():first_aws_rsa #ここでファイルの名前を記述して、エンター
Enter passphrase (empty for no passphrase): #何もせずそのままエンター
Enter same passphrase again: #何もせずそのままエンター
-----------------------------
$ vi config
-----------------------------
# 以下を追記
Host first_aws
  Hostname 54.64.22.197 #自分の設定に合わせて
  Port 22
  User shizuma #先ほどのユーザー名
  IdentityFile ~/.ssh/first_aws_rsa #秘密鍵の設定
-----------------------------

次に、サーバーに戻り作成した 公開鍵 をサーバーに設定します。

$ mkdir .ssh
$ chmod 700 .ssh
$ cd .ssh
$ vi authorized_keys
-----------------------------
#localの「first_aws_rsa.pub」の中身のコピペ。(localで $ cat first_aws_rsa.pubとかすると良い)
ssh-rsa sdfjerijgviodsjcIKJKJSDFJWIRJGIUVSDJFKCNZKXVNJSKDNVMJKNSFUIEJSDFNCJSKDNVJKDSNVJNVJKDSNVJKNXCMXCNMXNVMDSXCKLMKDLSMVKSDLMVKDSLMVKLCA shizuma@shizuma-no-MacBook-Air.local
-----------------------------
$ chmod 600 authorized_keys
$ exit
$ exit

これで設定が完了したので、以降作成したユーザーでアクセスするようにします。

基本ライブラリとrubyの環境構築

$ sudo yum install 
git make gcc-c++ patch 
openssl-devel 
libyaml-devel libffi-devel libicu-devel 
libxml2 libxslt libxml2-devel libxslt-devel 
zlib-devel readline-devel 
mysql mysql-server mysql-devel 
ImageMagick ImageMagick-devel 
epel-release
$ sudo yum install nodejs npm --enablerepo=epel
$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source .bash_profile
$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
$ rbenv rehash
$ rbenv install -v 2.3.1
$ rbenv global 2.3.1
$ rbenv rehash
$ ruby -v

gitの設定

最低限の設定だけしておきます。

$ vi .gitconfig
.gitignore
[user]
  name = your_name #自分の名前
  email = hoge@hoge.com #自分のメアド

[alias] #これはお好きに
  a = add
  b = branch
  ch = checkout
  st = status

[color] #色付け
  ui = true

[url "github:"] #pull、pushのための設定
    InsteadOf = https://github.com/
    InsteadOf = git@github.com:

アプリケーションフォルダの設置

/var/www/rails にアプリケーションフォルダを設置します。

$ cd /
$ sudo mkdir -p /var/www/rails
$ sudo chown -R shizuma var/www

GitHubの接続とアプリケーションのclone

$ cd ~/.ssh
$ ssh-keygen -t rsa
-----------------------------
Enter file in which to save the key ():aws_git_rsa #ここでファイルの名前を記述して、エンター
Enter passphrase (empty for no passphrase): #何もせずそのままエンター
Enter same passphrase again: #何もせずそのままエンター
-----------------------------
$ chmod 744 config
$ vi config
-----------------------------
# 以下を追記
Host github github.com
  Hostname github.com
  User git
  Port 22
  IdentityFile ~/.ssh/aws_git_rsa #秘密鍵の設定
-----------------------------
$ cat aws_git_rsa.pub
-----------------------------
ssh-rsa sdfjerijgviodsjcIKJKJSDFJWIRJGIUVSDJFKCNZKXVNJSKDNVMJKNSFUIEJSDFNCJSKDNVJKDSNVJNVJKDSNVJKNXCMXCNMXNVMDSXCKLMKDLSMVKSDLMVKDSLMVKLCA shizuma@ip-10-0-1-10
-----------------------------

ここで、これをコピペしてgithubに公開鍵を登録する。
githubへの鍵の登録がよくわからない方は以下の記事を参考に。
gitHubでssh接続する手順~公開鍵・秘密鍵の生成から~

そして、git clone する。

$ cd /var/www/rails
$ git clone https://github.com/kuboshizuma/cheerfull # 自分のアプリケーション

RDSの設定

EC2の環境構築に一区切りついたのでRDSの設定を行います。

DBサブネットの登録

RDSで使うサブネットを登録します。
この設定には異なるアベイラビリティゾーンにあるサブネットが最低1つずつ計2つ必要になります。
また、Railsアプリケーションをおいたサブネットはゲートウェイに繋がったpublicなサブネットなので、privateなサブネットを異なるアベイラビリティゾーンに一つずつ作成します。

スクリーンショット_2017-03-04_11_56_01.png

パラメータグループの設定

パラメータグループを設定します。
mysqlを使用していると設定なので、ここではmysqlを選択します。バージョンは適宜選択。
「chara…」で検索すると出て来るcharasetをutf-8に変更。

スクリーンショット_2017-03-04_12_00_57.png

設定が完了した後に、パラメータの編集を押してパラメーター変更。

スクリーンショット_2017-03-04_12_04_29.png

セキュリティグループの作成

VPCでセキュリティグループを作成します。

スクリーンショット_2017-03-04_12_08_19.png

インバウンドルールにMySQLを設定し、MySQLのアクセスのみ許可します。
「送信元」は0.0.0.0/0で設定。特定のRailsアプリケーションサーバーがあるサブネットに限定するほうがよさそう。

スクリーンショット 2017-03-04 12.55.42.png

RDSインスタンスの作成

エンジンの選択

スクリーンショット_2017-03-04_12_12_05.png

本番稼働用?

スクリーンショット_2017-03-04_12_12_45.png

DB詳細設定の指定

無料枠はt2.micro

スクリーンショット_2017-03-04_12_17_35.png

詳細設定の設定

各種作成したもので設定。

スクリーンショット_2017-03-04_12_19_44.png

スクリーンショット_2017-03-04_12_19_53.png

接続確認

hostは各自の作成したRDSインスタンスのエンドポイントをみる。

$ mysql -h hogepoge.ap-northeast-1.rds.amazonaws.com -u shizuma -P 3306 -p

接続出来たら完了!

絵文字の扱い

絵文字も登録出来るようにする場合。
以下を参考に utfmb4を採用する。

ActiveRecordをutf8mb4で動かす

MAMPでは /Applications/MAMP/conf/my.cnfmy.cnf を設置した。

Railsアプリケーションの起動のための準備

puma setting

以下を追記する。

cofig/puma.rb
# add setting for production
_proj_path = "#{File.expand_path("../..", __FILE__)}"
_proj_name = File.basename(_proj_path)
_home = ENV.fetch("HOME") { "/home/#{ENV['PUMA_USER']}" }
pidfile "#{_home}/run/#{_proj_name}.pid"
bind "unix://#{_home}/run/#{_proj_name}.sock"
directory _proj_path
# add end

ENV['PUMA_USER'] にサーバーのユーザー名が入るように環境変数を設定。

database setting

以下のように編集。

config/database.yml
production:
  <<: *default
  database: cheerfull
  username: <%= ENV['DATABASE_USER_NAME_PRODUCTION'] %>
  password: <%= ENV['DATABASE_PASSWORD_PRODUCTION'] %>
  host: <%= ENV['DATABASE_HOST_PRODUCTION'] %>

それぞれ該当の環境変数を設定。

rake secret

以下のsecret keyの箇所に値が入るように環境変数を設定。
rake secret とコマンドを打つと出て来る文字列を設定する。

config/secrets.yml
production:
  secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
$ gem install bundler
$ bundle install
$ rake db:create RAILS_ENV=production
$ rake db:migrate RAILS_ENV=production

PumaとNginxの起動

Pumaの起動

アプリケーションディレクトリにて以下を実行。

$ RAILS_SERVE_STATIC_FILES=true RAILS_ENV=production puma -w 4

「Ctr+c」でプロセス消さなければ生き残る。
プロセス消す必要があれば以下のようにする。

$ ps aux | grep puma # プロセスIDを探す
$ kill -9 (ID)

Nginx の起動

「cheerfull」の部分は自分のアプリケーションディレクトリ名に変更する。

/etc/nginx/conf.d/cheerfull.conf
  # log directory
  error_log  /var/www/rails/cheerfull/log/nginx.error.log;
  access_log /var/www/rails/cheerfull/log/nginx.access.log;
  upstream app_server {
    # for UNIX domain socket setups
    server unix:/home/shizuma/run/cheerfull.sock fail_timeout=0;
  }
  server {
    listen 80;
    server_name 12.134.156.178; # 自分のIP
    # nginx so increasing this is generally safe...
    # path for static files
    root /var/www/rails/cheerfull/public;
    # page cache loading
    try_files $uri/index.html $uri @app_server;
    location / {
      # HTTP headers
      proxy_pass http://app_server;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
    }
    # Rails error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/www/rails/cheerfull/public;
    }
    client_max_body_size 4G;
    keepalive_timeout 5;
  }

ユーザーをnginxから自分のユーザー名に変更しておく。

/etc/nginx/nginx.conf
#user nginx;
user shizuma;

nginxを起動します。

$ sudo service nginx restart

これで、IPアドレスでアクセスするとアプリケーションが表示されるようになりました。

Capistranoの設定

諸々動くことが確認出来たのでデプロイが出来るように設定します。
デプロイのためにCapistranoの設定をします。
慣れてきたら、いきなりCapistranoの設定をしていけばいいと思います。

socketの場所もアプリケーションディレクトリに変更するのでnginxの設定もそれに合わせて変更します。

「cheerfull」の部分は自分のアプリケーション名に変更する。

deploy.rb
require 'dotenv'
Dotenv.load

lock "3.7.1"

set :application, "cheerfull"
set :repo_url, "git@github.com:your_gitname/cheerfull.git"
set :branch, 'master'
set :deploy_to, '/var/www/rails/protospacce'
set :linked_files, fetch(:linked_files, []).push('.env')
set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system')
set :keep_releases, 5
set :rbenv_ruby, '2.3.1'

# puma setting
set :puma_threads,    [4, 16]
set :puma_workers,    0
set :pty,             true
set :use_sudo,        false
set :stage,           :production
set :deploy_via,      :remote_cache
set :deploy_to,       "/var/www/rails/#{fetch(:application)}"
set :puma_bind,       "unix://#{shared_path}/tmp/sockets/#{fetch(:application)}-puma.sock"
set :puma_state,      "#{shared_path}/tmp/pids/puma.state"
set :puma_pid,        "#{shared_path}/tmp/pids/puma.pid"
set :puma_access_log, "#{shared_path}/log/puma.error.log"
set :puma_error_log,  "#{shared_path}/log/puma.access.log"
set :puma_preload_app, true
set :puma_worker_timeout, nil
set :puma_init_active_record, true

namespace :deploy do
  desc 'Create database'
  task :db_create do
    on roles(:db) do |host|
      with rails_env: fetch(:rails_env) do
        within current_path do
          execute :bundle, :exec, :rake, 'db:create'
        end
      end
    end
  end
end

実際にdeployする前に、共有ファイルは用意しておきましょう。この場合、.env

ここでpumaのセッティングをしたので、config/puma.rb はもう不要になります。
また、dotenvを使用する場合は変更を読み込むために config/deploy/templates/puma.rb.erb を用意します。以下を用意。

config/deploy/templates/puma.rb.erb
#!/usr/bin/env puma

directory '<%= current_path %>'
rackup "<%=fetch(:puma_rackup)%>"
environment '<%= fetch(:puma_env) %>'
<% if fetch(:puma_tag) %>
  tag '<%= fetch(:puma_tag)%>'
<% end %>
pidfile "<%=fetch(:puma_pid)%>"
state_path "<%=fetch(:puma_state)%>"
stdout_redirect '<%=fetch(:puma_access_log)%>', '<%=fetch(:puma_error_log)%>', true


threads <%=fetch(:puma_threads).join(',')%>

<%= puma_bind %>
<% if fetch(:puma_control_app) %>
activate_control_app "<%= fetch(:puma_default_control_app) %>"
<% end %>
workers <%= puma_workers %>
<% if fetch(:puma_worker_timeout) %>
worker_timeout <%= fetch(:puma_worker_timeout).to_i %>
<% end %>

<% if puma_preload_app? %>
preload_app!
<% else %>
prune_bundler
<% end %>

on_restart do
  puts 'Refreshing Gemfile'
  ENV["BUNDLE_GEMFILE"] = "<%= fetch(:bundle_gemfile, "#{current_path}/Gemfile") %>"
  ENV.update Dotenv::Environment.new('.env')
end

<% if puma_preload_app? and fetch(:puma_init_active_record) %>
before_fork do
  ActiveRecord::Base.connection_pool.disconnect!
end

on_worker_boot do
  ActiveSupport.on_load(:active_record) do
    ActiveRecord::Base.establish_connection
  end
end
<% end %>
$ bundle exec production cap puma:config 

これでこの設定を本番に反映。

あとはデプロイコマンドを打つ。

$ bundle exec cap production deploy

デプロイdone!

nginxのlogの位置やsocketの位置を変更します。

/etc/nginx/conf.d/cheerfull.conf
  # log directory
  error_log  /var/www/rails/cheerfull/shared/log/nginx.error.log;
  access_log /var/www/rails/cheerfull/shared/nginx.access.log;
  upstream app_server {
    # for UNIX domain socket setups
    server unix:/var/www/rails/cheerfull/shared/tmp/sockets/cheerfull-puma.sock fail_timeout=0;
  }
  server {
    listen 80;
    server_name 12.134.156.178; # 自分のIP
    # nginx so increasing this is generally safe...
    # path for static files
    root /var/www/rails/cheerfull/current/public;
    # page cache loading
    try_files $uri/index.html $uri @app_server;
    location / {
      # HTTP headers
      proxy_pass http://app_server;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $http_host;
      proxy_redirect off;
    }
    # Rails error pages
    error_page 500 502 503 504 /500.html;
    location = /500.html {
      root /var/www/rails/cheerfull/current/public;
    }
    client_max_body_size 4G;
    keepalive_timeout 5;
  }
$ sudo service nginx restart

これで、デプロイ環境が整いました。
以前起動したpumaのプロセスはきっておきましょう。

複数サーバーへのデプロイ

multiconfig を使用します。

Gemfile
gem 'capistrano-multiconfig', '~> 3.1', require: false

Capfileのcapistrano/setupから変更。

Capfile
# require "capistrano/setup"
require 'capistrano/multiconfig'

config/deploy/production.rb から config/deploy/app1/production.rb に変更する。
以下のようにdeploy出来るようになる。

$ bundle exec cap app1:production deploy

違うサーバーにデプロイするときは同じように config/deploy/app2/production.rb を準備すればオーケー。

$ bundle exec cap app2:production deploy

これで違うサーバーにデプロイ出来る。

これまでと同様にec2サーバーをもう一度用意して2箇所にデプロイ出来るようにする。

ロードバランサー ELB

ロードバランサーをhttpsで運用したいときadmin@hoge.comのようなアドレスへのメールを受信可能のしないといけない。

以下でとりあえずzoneファイルのインポートまでする。
Amazon Route53 ネームサーバへの移行手順(お名前.comからの)

その後にこの記事にあるようにお名前.com側の設定をする。
お名前.com + Route53で独自ドメインのメールをGmailに転送する

メールの転送が出来ていることを確認する。
そして、ELBの指示に従って設定。
下記参考。
【初心者向け】ELBにSSL証明書をインストールする

そこまでしたら
Amazon Route53 ネームサーバへの移行手順(お名前.comからの)
の続きでELBとつなげる。

注意点は、ターゲットグループにトラフィックをルーティングするときのプロトコルをHTTPにすること。これでELBへはHTTPSでアクセス出来、それをアプリケーションではHTTPで扱うことが出来る。

また、以下のように nginxのサーバーネームにドメイン名を追加する必要がある。

server_name hogepoge.com 00.000.00.00 sakamichi-app-elb-000000000.ap-northeast-1.elb.amazonaws.com

CloudFront

Amazon CloudFront + ACM 独自ドメインで HTTPS (SSL) 配信設定メモ

CloudWatchとか

必要の応じて加筆。

続きを読む