Amazon RDSにSSHトンネルでIntelliJ IDEAから接続した時のメモ

RDSのパブリックアクセスを「いいえ」にしている状況で、IntelliJ IDEAから接続したい。という状況での設定メモ。
RDSにかぎらず、踏み台サーバ使うときは同様の設定方法でいけるはず。

RDSにアクセス可能なEC2インスタンス1台にElastic IPでIPが振られている状態で、
sshクライアントからアクセス可能な状態を想定しています。

データ・ソースおよびドライバー

一般

ここはローカルのDBなど直接アクセスできるDBに接続するときと同じ設定です。

20171211_1.png

ホスト: AWSコンソールのRDSの[エンドポイント]
データベース: AWSコンソールの[DB名]
ユーザー: AWSコンソールの[ユーザ名]
パスワード: インスタンス生成時に入力したパスワード

SSH/SSL

SSHトンネルの設定をします。

20171211_2.png

プロキシー・ホスト: インスタンスに設定したElastic IP
ポート: 22 (sshdの設定を変更していればその値)
プロキシー・ユーザー: ubuntu (ubuntuでインスタンス生成したとき、AMIはec2-user)
認証タイプ: Key pair
プライベート・キー・ファイル: キーペアで設定した秘密鍵を設定

こんな感じで出来ました。

mysqlコマンドで接続したときは

$ ssh -N -L 3307:appname.aaaaaaa.ap-northeast-1.rds.amazonaws.com:3306 -i ~/.ssh/id_rsa -p 22 ubuntu@host

とした後に、

$ mysql -u username -p -h 127.0.0.1 --port=3307

で接続できました。

続きを読む

Docker + Nginx + Let’s EncryptでHTTPS対応のプロキシサーバーを構築する

Docker上にNginxコンテナをプロキシサーバーとして構築し、Let’s EncryptでHTTPS対応しました。構築にあたって かなり苦戦した ので、そのノウハウを記事としてまとめました。

「Nginx」とは

Apacheなどの従来のWebサーバーは、クライアントの数が多くなるとサーバーがパンクする 「C10K問題(クライアント1万台問題)」 を抱えていました。「Nginx」はこの問題を解決するために誕生した、静的コンテンツを高速配信するWebサーバーです。2017年10月現在、そのシェアは Apacheとほぼ同等 となっています。

wpid-wss-share13.png

Webサーバー シェア
Micosoft IIS 49.44%
Apache 18.78%
Nginx 18.40%

「Let’s Encrypt」とは

「Let’s Encrypt」は すべてのWebサーバへの接続を暗号化する ことを目指し、SSL/TLSサーバ証明書を 無料 で発行する認証局(CA)です。シスコ、Akamai、電子フロンティア財団、モジラ財団などの大手企業・団体がスポンサーとして支援しています。


本稿が目指すシステム構成

本稿ではAmazon EC2、Dockerコンテナを使用して以下のようなシステムを構築することを目標とします。

DockerでNgixのプロキシサーバーを構築する.png

前提条件

  • 独自ドメインを取得していること(本稿で使用するドメインはexample.comとします)
  • IPv4パブリックIP(Elastic IP)がEC2インスタンスに設定されていること
  • EC2インスタンスにDocker、docker-composeがインストールされていること

事前に準備すること

DockerでHTTPS対応のプロキシサーバーを構築するにあたり、事前に以下の設定をしておく必要があります。

  • EC2のインバウンドルールで443ポートを開放する
  • DNSのAレコードを設定する
  • プロキシ用のネットワークを構築する

EC2のインバウンドルールで443ポートを開放する

インバウンドルールを以下のように設定し、443ポートを外部へ公開します。

タイプ プロトコル ポート範囲 ソース
HTTPS TCP 443 0.0.0.0/0
HTTPS TCP 443 ::/0

DNSのAレコードを設定する

DNSの設定方法は利用しているドメイン取得サービスによって異なります。例えばバリュードメインの場合、DNSの設定方法は「DNS情報・URL転送の設定 | VALUE-DOMAIN ユーザーガイド」に記載されています。

DNSのAレコードを以下のように設定します。xx.xx.xx.xxにはEC2インスタンスに割り当てられているIPv4パブリックIPを設定します。

a @ xx.xx.xx.xx
a www xx.xx.xx.xx

上記設定は以下を意味します。

  • example.com(サブドメイン無し)をIPアドレスxx.xx.xx.xxにポイントする
  • www.example.com をIPアドレスxx.xx.xx.xxにポイントする

プロキシ用のネットワークを構築する

プロキシサーバーとWebサーバー間のネットワークは外部との通信を行う必要がありません。そこで
プロキシサーバーとWebサーバー間の 内部ネットワーク を構築するため、EC2のインスタンスにログインし、以下のコマンドを入力します。

$ docker network create --internal sample_proxy_nw

上記コマンドは以下を意味します。

  • --internal: ネットワーク外との通信が行えないネットワークを作成します。
  • sample_proxy_nw: 任意のネットワーク名です。

以下のコマンドを入力し、ネットワークの設定情報がコンソールに出力されていることを確認しましょう。

$ docker network inspect sample_proxy_nw

Dockerコンテナの定義ファイルを作成する

事前準備が完了したら、Dockerコンテナの定義ファイルを作成しましょう。本稿におけるディレクトリ構成は以下のとおりです。

/path/to/dir/

.
├── docker-compose.yml // プロキシサーバーとWebサーバーのコンテナを定義するファイル
└── proxy
    ├── default.conf // プロキシサーバー上にあるNginxのデフォルト定義ファイル
    ├── Dockerfile // プロキシサーバーのイメージを構築するためのファイル
    └── entrypoint.sh // プロキシサーバーにSSL証明書を取得するためのファイル

以下では、各ファイルの内容を解説します。

./docker-compose.yml

docker-compose.ymlでは、以下のコンテナを定義しています。

  • proxy: プロキシサーバー(Nginxベース)
  • web1: Webサーバー(httpdベース)
  • web2: Webサーバー(httpdベース)
version: '3'
services:
  proxy:
    build: ./proxy
    tty: true
    image: sample_proxy
    container_name: sample_proxy
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    ports:
      - "443:443"
    volumes:
      - '/srv/letsencrypt:/etc/letsencrypt'
    networks:
      - default
      - sample_proxy_nw
    depends_on:
      - "web1"
      - "web2"
    command: ["wait-for-it.sh", "sample_web1:80", "--", "./wait-for-it.sh", "sample_web2:80"]
  web1:
    image: httpd
    container_name: sample_web1
    tty: true
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - sample_proxy_nw
  web2:
    image: httpd
    container_name: sample_web2
    tty: true
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    networks:
      - sample_proxy_nw
networks:
  proxy_nw:
    external: true

上記コマンドは以下を意味します。

  • サービスproxyports: 外部からのHTTPSアクセスとproxyサーバーの内部ポートを疎通させるため、443:443を定義します。
  • サービスproxyvolumes: /srv/letsencrypt:/etc/letsencryptを定義します。/etc/letsencryptLet’s Encryptで取得した証明書が生成されるディレクトリ です。
  • networks: 上述の説明で生成したsample_proxy_nwを各サービス(proxy, web1, web2)に定義します。
  • depends_on: コンテナの起動順序を制御するオプションです。 Nginxのproxy_passに設定されているWebサーバーが起動していない状態でプロキシサーバーが起動した場合にエラーとなる ため、web1, web2を設定します。

./proxy/default.conf

./proxy/default.confはNginxのデフォルト定義ファイル(/etc/nginx/conf.d/default.conf)を書き換えるためのファイルです。

server{

    server_name example.com www.example.com;

    proxy_redirect off;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Server $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location / {
        proxy_pass    http://sample_web1/;
    }

    location /example/ {
        proxy_pass    http://sample_web2/;
    }

}

上記設定は以下を意味します。

  • server_name: ユーザーから要求されるHTTPリクエストのヘッダに含まれるHostフィールドとserver_nameが一致した場合、該当するサーバ設定を採用します。Nginxではキャッチオールサーバーとして_を定義することもできますが、 certbot-autoがサーバー情報を正しく取得することができない ため、上記のようにドメイン名を入力します。
  • location: ルートディレクトリ(example.com/)とサブディレクトリ(example.com/example/)にアクセスした際の振り分け先URIを設定します。proxy_passには、http://[コンテナ名]/を設定します。コンテナ名はdocker-compose.ymlのcontainer_nameで設定した名前となります。
    また、http://sample_web1/のように 末尾に/を入れる ことに注意しましょう。例えばlocation /example/において、プロキシパスの末尾に/が含まれていない(http://sample_web2)場合、振り分け先は http://sample_web2/example/となってしまいます。

./proxy/Dockerfile

FROM nginx
COPY default.conf /etc/nginx/conf.d/default.conf
RUN apt-get update && apt-get install -y \
        wget && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
ADD https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh /usr/local/bin/wait-for-it.sh
RUN chmod +x /usr/local/bin/wait-for-it.sh
ADD https://dl.eff.org/certbot-auto /usr/local/bin/certbot-auto
RUN chmod a+x /usr/local/bin/certbot-auto
RUN certbot-auto --os-packages-only -n
COPY ./entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

上記設定は以下を意味します。

  • ADD https://dl.eff.org/certbot-auto /usr/local/bin/certbot-auto: Let’s Encryptが発行するSSL/TLSサーバ証明書を自動で取得・更新するツール「 certbot-auto 」をダウンロードします。

./proxy/entrypoint.sh

#!/bin/bash
certbot-auto --nginx -d example.com -d www.example.com -m your-account@gmail.com --agree-tos -n
certbot-auto renew
/bin/bash

上記設定は以下を意味します。

  • --nginx: プロキシサーバーにNginxを使用する場合のオプションです。default.confの設定を自動的に書き換えます。(2017年12月現在、アルファ版のプラグイン)
  • -d example.com -d www.example.com: SSL/TLSサーバ証明書の取得を申請するドメイン名を指定します。
  • -m your-account@gmail.com: アカウントの登録や回復などに使用する電子メールアドレスを指定します。
  • --agree-tos: Let’s Encryptの利用規約に同意します。
  • -n: インタラクティブ設定をオフにします。
  • ./certbot-auto renew: 3ヶ月で失効する SSL/TLSサーバ証明書を自動で更新します。

以下のコマンドを入力してentrypoint.shに 実行権限を付与する ことを忘れないようにしましょう。

$ chmod +x entrypoint.sh

Dockerコンテナを起動する

それでは以下のコマンドを入力してDockerコンテナを起動しましょう。

docker-compose up -d

しばらく時間をおいてから、以下のコマンドを入力します。

docker-compose logs

以下のように出力されていれば成功です。

-------------------------------------------------------------------------------
Congratulations! You have successfully enabled https://example,com and
https://www.example.com

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=example.com
https://www.ssllabs.com/ssltest/analyze.html?d=www.example.com
-------------------------------------------------------------------------------

HTTPSでアクセスする

ブラウザを起動し、実際に以下のURLにアクセスしてみましょう。

Chromeブラウザの場合はデベロッパーツール > Security > View certificateからSSL/TLSサーバ証明書を確認することができます。

「発行元: Let’s Encrypt Authority X3」となっているはずです。

続きを読む

ラズパイでスマートスピーカーを自作(stretch)

myalexa.jpeg

ポストスマホの有力候補といえばスマートスピーカーということで、とりあえずデバイス側を作ってみました。
今回は米国で既に先行しているamazon Alexaとやり取りしたいと思います。
なんか色々アレクサと対話したいので英語、米語、ドイツ語、日本語と切り替えれるようにしました。

用意するもの

  • プロト基盤:Raspberry Pi3(2でも良いがwifiが付いているので3)
  • スピーカー:USB,3.5mmでもいいしHDMIでもいい
  • mircoSD:RSPIとの依存があるのでこの中から選ぶことを強くおすすめします。
  • USBキーボード:ラズパイ操作用
  • USBマウス:ラズパイ操作用
  • HDMIケーブル:モニターにつなぐため
  • HDMIモニタ:ラズパイ操作用
  • macbook:OSダウンロード用

大まかな流れ

ラズパイの設定
1. OSインストール
2. OSセットアップ
AVSの構築
1. SDKインストールと環境設定
2. SDKのビルド
3. AVSの認証
4. 実行

はい、では行ってみよう。

ラズパイの設定

1. OSインストール
まずはOS(raspbian)をインストールしましょう。
1. 1 OSのダウンロード
このサイトからダウンロード(https://www.raspberrypi.org/downloads/)

今回はraspbianというDebianベースのOSを使います。
ここにはNoobsという初心者用のインストーラーもあるけど、イケてる君は
Bootイメージを直接扱った方が時短になるので迷わずRasbian stretch2017-09-07を選ぼう。
※国内外の文献ではAVSのSDKがstretchに対応していないと書いているけど、そこはクリアしているので大丈夫。

1.2 SDカードのフォーマット

  • macbookにmicroSDカードを差す。
  • マウントが出来たらSDカードのフォーマットを行う。
  • SDカードの場所を確認
    $diskutil list
    これでSDカードのディレクトリが分かります。(ex:/dev/disk2)

  • SDカードのフォーマット(FATね)
    $sudo diskutil eraseDisk FAT32 RASBIAN MBRFormat /dev/disk2

    SDカードのサイズが30GB以下はFAT32とし、それ以上のサイズはexFATする。
    RASBIANは任意で好きな名前を付けてね

  • SDカードをアンマウントし、コピーができる状態が完了
    $sudo diskuitl unmount /dev/disk2
    disk2をアンマウントする

1.3 SDカードのコピー

  • ダウンロードしたOSを解凍
    $tar xzf 2017-09-07-raspbian-stretch.zip

  • 解凍したイメージをSDカードにコピー
    $sudo dd bs=1m if=2017-09-07-raspbian-stretch.img of=/deb/rdsik2

    なぜデバイス名rdiskになってるの?とお気づきですか。
    それはrdiskの方が書き込む速度が早いからです。
    disk2もrdisk2も同じ場所ですが、disk2とした場合、ランダムアクセスが可能となるデバイスとして転送データがUserSpaceという4KBのバッファを経由して処理されます。
    これはマルチタスクがゆえのことなのですが、これをrdiskで指定することにより、バッファを経由せずにダイレクトでシーケンシャルに処理が進むためにdiskよりも高速になります。
    興味と時間のある方はdisk指定で4GBのコピーの旅をお楽しみ下さい。

2 OSセットアップ
いよいよラズパイを動かしましょう。

2.1 OSの起動
RSPI3にモニタ、キーボード、マウス、SDカードを入れて電源ON
rasbian.jpeg

2.2 初期セットアップ

  • ターミナルを起動し次の設定を行う
    sudo raspi-configでもいいしGUIでもOK。とにかくこれを設定
    ・タイムゾーンの設定
    ・wifiの設定
    ・SSHの設定
    ・キーボード設定
    ※お好みに合わせてVNCも設定

AVSの構築

ここから本番。
1. SDKインストールと環境設定
1.1 まずは最低限必要なパッケージの更新とインストール

  • パッケージの更新
    sudo apt-get update

  • で、必要なパッケージをインストール

sudo apt-get -y install git gcc cmake build-essential libsqlite3-dev libcurl4-openssl-dev libfaad-dev libsoup2.4-dev libgcrypt20-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-good libasound2-dev doxygen

1.2 開発環境のディレクトリを作成

cd /home/pi/ && mkdir sdk-folder && cd sdk-folder && mkdir sdk-build sdk-source third-party application-necessities && cd application-necessities && mkdir sound-files

1.3 フリーの音声ライブラリ(portaudio)を拝借
ディレクトリ移動&ダウンロード&解凍&configure&makeを一気に行います

cd /home/pi/sdk-folder/third-party && wget -c http://www.portaudio.com/archives/pa_stable_v190600_20161030.tgz && tar zxf pa_stable_v190600_20161030.tgz && cd portaudio && ./configure --without-jack && make

1.4 commentjsonのインストール
AVS認証時に必要になるAlexaClientSDKConfig.jsonに書き込みを行うために必要
pip install commentjson

1.5 タイマーとアラーム音をアマゾンのサイトからダウンロード

cd /home/pi/sdk-folder/application-necessities/sound-files/ && wget -c https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/dex/alexa/alexa-voice-service/docs/audio/states/med_system_alerts_melodic_02._TTH_.mp3 && wget -c https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/dex/alexa/alexa-voice-service/docs/audio/states/med_system_alerts_melodic_02_short._TTH_.wav && wget -c https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/dex/alexa/alexa-voice-service/docs/audio/states/med_system_alerts_melodic_01._TTH_.mp3 && wget -c https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/dex/alexa/alexa-voice-service/docs/audio/states/med_system_alerts_melodic_01_short._TTH_.wav

2. SDKのビルド
2.1 AVSのSDKをクローンします

cd /home/pi/sdk-folder/sdk-source && git clone git://github.com/alexa/avs-device-sdk.git

2.2 ウェイクワードのエンジン(Sensory)もクローン
これをすると「アレクサ!」で反応してくれるようになります。

cd /home/pi/sdk-folder/third-party && git clone git://github.com/Sensory/alexa-rpi.git

で、それの利用規約に同意します。

cd /home/pi/sdk-folder/third-party/alexa-rpi/bin/ && ./license.sh 

2.3 日本語化対応
デフォルトは英語なので、これらのソースを変更し日本語に対応させます。

まずはエンドポイントを日本に変更

/sdk-folder/sdk-source/avs-device-sdk/SampleApp/src/SampleApplication.cpp
//68行目
// Default AVS endpoint to connect to.
static const std::string DEFAULT_ENDPOINT("https://avs-alexa-fe.amazon.com");

アプリ実行中のメニューに日本語切り替えを追加

/sdk-folder/sdk-source/avs-device-sdk/SampleApp/src/UIManager.cpp
//89行目のこのメニューに日本語を追加し、英語と入れ替える
static const std::string LOCALE_MESSAGE =
    "+----------------------------------------------------------------------------+n"
    "|                          Language Options:                                 |n"
    "|                                                                            |n"
    "| Press '1' followed by Enter to change the language to US English.          |n"
    "| Press '2' followed by Enter to change the language to UK English.          |n"
    "| Press '3' followed by Enter to change the language to German.              |n"
   "+----------------------------------------------------------------------------+n";
//を、次の内容に変更する
static const std::string LOCALE_MESSAGE =
    "+----------------------------------------------------------------------------+n"
    "|                          Language Options:                                 |n"
    "|                                                                            |n"
    "| Press '1' followed by Enter to change the language to Japan.               |n"
    "| Press '2' followed by Enter to change the language to UK English.          |n"
    "| Press '3' followed by Enter to change the language to German.              |n"
    "| Press '4' followed by Enter to change the language to US English.          |n"
    "+----------------------------------------------------------------------------+n";

メニューの変更をテーブルにも反映

/sdk-folder/sdk-source/avs-device-sdk/SampleApp/src/UIManager.cpp
//44行目 日本語を追加し、英語と入れ替える
static const std::unordered_map<char, std::string> LOCALE_VALUES({{'1', "ja-JP"}, {'2', "en-GB"}, {'3', "de-DE"},{'4', "en-US"}});

2.4 AVSのSDKをビルド
まずはcmakeを走らせます。
ウェイクワードをONやgstreamer許可なのをオプションとして設定しています。

cd /home/pi/sdk-folder/sdk-build && cmake /home/pi/sdk-folder/sdk-source/avs-device-sdk -DSENSORY_KEY_WORD_DETECTOR=ON -DSENSORY_KEY_WORD_DETECTOR_LIB_PATH=/home/pi/sdk-folder/third-party/alexa-rpi/lib/libsnsr.a -DSENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR=/home/pi/sdk-folder/third-party/alexa-rpi/include -DGSTREAMER_MEDIA_PLAYER=ON -DPORTAUDIO=ON -DPORTAUDIO_LIB_PATH=/home/pi/sdk-folder/third-party/portaudio/lib/.libs/libportaudio.a -DPORTAUDIO_INCLUDE_DIR=/home/pi/sdk-folder/third-party/portaudio/include

そしてmake
make SampleApp -j3

makeのjオプションは並行処理の数で多い方がより高速になるがリスクもあるので、とりあえず3程度で。安全なのはもちろんjオプションなし。

3. AVSの認証
3.1 プロダクトの登録
amazonへサインインして今回のプロダクトを登録します。
入力方法はこちらを参考に(https://github.com/alexa/alexa-avs-sample-app/wiki/Create-Security-Profile)

この登録で設定したProductID,ClientID,ClientSecretの3つは認証時に必ず必要になるよ!

3.2 AlexaClientSDKConfig.jsonの設定
これは認証実行時に必要な情報を定義する設定ファイルです

場所:/home/pi/sdk-folder/sdk-build/Integration
ファイル名:AlexaClientSDKConfig.json

AlexaClientConfig.json
{
    "authDelegate":{
        "clientSecret”:”Amazonへ設定したClientSecret”,
        "deviceSerialNumber":"123456,(適当でいい)
        "refreshToken”:”{デフォルトの状態にしておく、認証後に自動的に追加されるため}”,
        "clientId”:”Amazonへ設定したclientID”,
        "productId”:”Amazonへ設定したproductID”
    },
    "alertsCapabilityAgent":{
        "databaseFilePath":"/home/pi/sdk-folder/application-necessities/alerts.db",
        "alarmSoundFilePath":"/home/pi/sdk-folder/application-necessities/sound-files/med_system_alerts_melodic_01._TTH_.mp3",
        "alarmShortSoundFilePath":"/home/pi/sdk-folder/application-necessities/sound-files/med_system_alerts_melodic_01_short._TTH_.wav",
        "timerSoundFilePath":"/home/pi/sdk-folder/application-necessities/sound-files/med_system_alerts_melodic_02._TTH_.mp3",
        "timerShortSoundFilePath":"/home/pi/sdk-folder/application-necessities/sound-files/med_system_alerts_melodic_02_short._TTH_.wav"
    },
    "settings":{
        "databaseFilePath":"/home/pi/sdk-folder/application-necessities/settings.db",
        "defaultAVSClientSettings":{
            "locale":"ja-JP"
        }
    },
    "certifiedSender":{
        "databaseFilePath":"/home/pi/sdk-folder/application-necessities/certifiedSender.db
    }
}

3.3 認証
認証用プログラムを起動、3000ポートで待ち受けます
cd /home/pi/sdk-folder/sdk-build && python AuthServer/AuthServer.py

ブラウザを立ち上げhttp://localhost:3000へアクセス
Developer用のアカウントを聞かれるのでログイン
ログインが成功するとWindowを閉じろというメッセージが出て認証完了

※AlexaClientSDKConfig.jsonのrefreshTokenに新たなトークンが追加されていることが確認できます。

4 実行
4.1 マイクのテスト
テストの前にpiのカレントに設定ファイルを用意します。

ファイル名:.asoundrc
場所:ユーザーカレントディレクトリ

.asoundrc
pcm.!default{
    type asym
    playback.pcm {
        type plug
        slave.pcm "hw:0,0"
    }
    capture.pcm {
        type plug
        slave.pcm "hw:1,0"
    }
}

※playbackは再生、captureは入力の設定ブロックになっている。
slave.pcmの値hw:はa,b a=カード b=デバイス番号となっている。
ラズパイに差したスピーカーとマイクの値を確認し設定すること。
aplay -lで確認できる。

4.2 soxのインストール
音声の加工や編集が出来るコマンドですー。
sudo apt-get install sox -y

4.3 マイクとスピーカーの入出力テスト
rec test.wav

実行したら何か話してみよう。
声に合わせて入力ボリュームが変化している様子がコマンドから分かると思う。
それが確認できたらControl+Cで終了

`aplay /usr/share/sounds/alsa/Front_Center.wav’

「フロントセンタ〜」っていう女性の声が聞こえたらOK
もし聞こえない場合はスピーカーの接続種別により次の設定を行って下さい

sudo amixer cset numid=3 x

xは接続の種類です。
0:自動判別
1:ライン入力(3.5mm)
2:HDMI
自動判別の場合はHDMIが優先されます。

4.4 いよいよAlexa起動
srcに移動
cd /home/pi/sdk-folder/sdk-build/SampleApp/src
SampleAPPを起動

TZ=UTC ./SampleApp /home/pi/sdk-folder/sdk-build/Integration/AlexaClientSDKConfig.json /home/pi/sdk-folder/third-party/alexa-rpi/models

SampleAPPの引数に設定ファイルであるAlexaClientSDKConfig.jsonとmodelsを指定

4.5 「Alexa! こんにちは!」と話しかけてみよう。
あとは、スマホにAlexa APPを追加するかここから好きなスキルを追加してみましょう。

カスタムスキルの作り方(Alexa Skills KitとLambda)はいろんな人が書いてるのでそちらを見てください。

続きを読む

本番環境をGCP/AWSで何個か作り、インフラについて少しわかったこと【GCP環境構築編】

Hakusan Mafiaアドベントカレンダー5日目を余語 [Qiita|facebook|github] が担当します!

本番環境をGCP/AWSで何個か作り、インフラについて少しわかったこと【SSL証明書編】
本番環境をGCP/AWSで何個か作り、インフラについて少しわかったこと【GCP環境構築編】 ←今これ
本番環境をGCP/AWSで何個か作り、インフラについて少しわかったこと【パフォーマンス改善編】

はじめに

以前はAWSでインフラ環境を整えるのがベストプラクティスかと思っていました。というのも、EC2は直感的に触って起動できるし、S3・RDS・ DynamoDMなどの記事も相当Webに溢れていたので簡単でした。ただ、プレスリリースを売ったりメディアに出したりする案件があった際にわざわざAmazonへ申請を出さなければならないといけないらしいということを聞いて少し疑問に感じた時もありました。

そんな時に、AWSと同等のものを用意してくれている。且つ、自動スケーリングが凄い(詳しくいうと、ロードバランサがGoogle検索と同じらしい)という記事を見た時に、少々衝撃を覚えました。

今回の記事では、Google Cloud Platform(GCP)を利用して本番環境を構築する方法を記述します。
基本的には、公式ドキュメント通りにやると全てうまくいくのですが、自分のターミナルから色々いじりたい人用に書いてます。

10分でGCP環境構築

1. ログイン、プロジェクト作成

https://cloud.google.com/?hl=ja の右上でログインし、その後「コンソール」へ入る
プロジェクト作成から、「プロジェクト名」を入力して、準備完了

2. GCEを選択し、VMインスタンス作成

https://gyazo.com/0581708139112c11db672d5cdcfb7ea6

名前は適当で、
- ゾーンを「asia-northeast1-a」
- ブートディスクに今回は、「CentOS」
- ファイアウォールの設定は二つともチェックを入れましょう。

https://gyazo.com/5cc259ec583d4635073f1876cbbd28e6

3. Terminalにてgcloudログイン

今回は、ミドルウェア、とりわけWebサーバーをNginx、ApサーバーをUnicornで実装します。
OSは先ほどCentOSを利用すると選択したので、webにあるDebianでの記事と比較しながらやると勉強になると思います。
(RoRアプリケーションを想定しています。)

ミドルウェアの設定は次の通りです。

ミドルウェア 項目
nginx conn./worker 1024
unicorn worker processes/cpu 2 or 3

インスタンス作成後に、そのVMインスタンスの「接続」から「glcloud コマンドを表示」という箇所でコマンドをコピーして、ローカルで接続して見てください。

local
$ gcloud compute --project "xxxxx-yyy-1111111" ssh --zone "asia-northeast1-a" "xxx"
Last login: Sun Dec  3 08:13:59 2017 from softbankxxxx.bbtec.net

server
[xxxx@yyyy ~]$ #こんな感じでログインできる

先ほどGoogle Compute Engineで作成したVMインスタンスにログインできる。
(ここで弾かれたら、permission関連なのでgithubのSSH and GPG keysという箇所に公開鍵を貼り、ログインしてください。)

gcloudがないと言われたら、下記でインストール&ログイン

local
$ curl https://sdk.cloud.google.com | bash
$ gcloud init

4. 権限管理・ミドルウェアのインストール

4.1 新規ユーザー作成

server

[user_name| ~ ]$ sudo adduser [新規ユーザー名]
[user_name| ~ ]$ sudo passwd [新規ユーザー名]
[user_name| ~ ]$ sudo visudo
----------------------------
root ALL=(ALL) ALL
[新規ユーザー名] ALL=(ALL) ALL # この行を追加
# ↑後々 wheelか何かで管理できるとなお良い
----------------------------
[user_name| ~ ]$ sudo su - [新規ユーザー名]

4.2 VMインスタンス内の環境構築

yum -> node.js -> rbenv/ruby-build -> rubyの順

4.2.1 yum (mysql関連含む)

server
[user_name| ~ ]$ sudo yum install 
                 git make gcc-c++ patch 
                 libyaml-devel libffi-devel libicu-devel 
                 zlib-devel readline-devel mysql-server mysql-devel  -y

4.2.2 node.js

server
[user_name| ~ ]$ sudo curl -sL https://rpm.nodesource.com/setup_6.x | sudo bash -
[user_name| ~ ]$ sudo yum install -y nodejs

4.2.3 rbenv/ruby-build

server
[user_name| ~ ]$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
[user_name| ~ ]$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
[user_name| ~ ]$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
[user_name| ~ ]$ source ~/.bash_profile
[user_name| ~ ]$ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
[user_name| ~ ]$ rbenv rehash

4.2.4 ruby

server
# centOSを選択した場合
[user_name| ~ ]$ sudo yum -y install bzip2

[user_name| ~ ]$ rbenv install -v 2.4.1
[user_name| ~ ]$ rbenv global 2.4.1
[user_name| ~ ]$ rbenv rehash
[user_name| ~ ]$ ruby -v
ruby 2.4.1p111 (2017-03-22 revision 58053) [x86_64-linux]

4.3 Gitとインスタンスの紐ずけ

ssh接続 → git clone

server
[user_name@vm_name .ssh]$ ssh-keygen -t rsa
[user_name@vm_name .ssh]$ cat config 
Host github
  Hostname github.com
  User git
  IdentityFile ~/.ssh/id_rsa #<- 追加
[user_name@vm_name .ssh]$ ssh -T github
# root権限だとgit cloneできないため
[user_name| ~ ]$ sudo chown user_name www/
[user_name@vm_name www]$ pwd
/var/www
[user_name@vm_name www]$ git clone git@github.com:~~~~

4.4 Nginxを積む

server
[ユーザー名|~]$ sudo yum install nginx
[ユーザー名|~]$ cd /etc/nginx/conf.d/
[ユーザー名|~]$ sudo vi <your_app_name>.conf (小文字でもok)
# default.conf/ssl.confなどと分けるとなお良い。(細分化)

Nginxの設定は最小限です。

/etc/nginx/conf.d/your_app_name.conf
upstream unicorn {
    server  unix:/var/www/<your_app_name>/tmp/sockets/unicorn.sock;
}

server {
    listen       80;
    server_name  <ip address and/or domain name>;

    access_log  /var/log/nginx/access.log;
    error_log   /var/log/nginx/error.log;

    root /var/www/<your_app_name>/public;

    client_max_body_size 100m;
    error_page  404              /404.html;
    error_page  500 502 503 504  /500.html;
    try_files   $uri/index.html $uri @unicorn;

    location @unicorn {
        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;
    }
}
server
[user_name|~]$ cd /var/lib
[user_name|lib]$ sudo chmod -R 775 nginx #パーミッション調整

4.5 Unicornの導入/設定

server
[user_name@vm_name app_name]$  vi Gemfile
---------------------
group :production do
    gem 'unicorn'
end
---------------------
# gem: command not found
[user_name@vm_name app_name]$ gem install bundler
[user_name@vm_name app_name]$ bundle install
[user_name@vm_name app_name]$ vim config/unicorn.conf.rb
/var/www/app_name/config/unicorn.conf.rb
$worker = 2
$timeout = 30
$app_dir = '/var/www/<App_name>'
$listen  = File.expand_path 'tmp/sockets/unicorn.sock', $app_dir
$pid     = File.expand_path 'tmp/pids/unicorn.pid', $app_dir
$std_log = File.expand_path 'log/unicorn.log', $app_dir
worker_processes  $worker
working_directory $app_dir
stderr_path $std_log
stdout_path $std_log
timeout $timeout
listen  $listen
pid $pid
preload_app true

before_fork do |server, _worker|
  defined?(ActiveRecord::Base) && ActiveRecord::Base.connection.disconnect!
  old_pid = "#{server.config[:pid]}.oldbin"
  if 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) && ActiveRecord::Base.establish_connection
end

4.6 CSS/JSをコンパイル

server
[user_name@vm_name app_name]$ bundle exec rake assets:precompile RAILS_ENV=production

4.7 Unicorn起動

server
[user_name@vm_name app_name]$ bundle exec unicorn_rails -c /var/www/<your_app_name>/config/unicorn.conf.rb -D -E production

4.7 Nginx再起動

server
[user_name@vm_name app_name]$ sudo service nginx reload

これで本番環境が構築できたかと思います。
エラーが出た際は、下記を参考にデバッグをすれば問題ないので、各自調べてください。

nginx   なら /var/log/nginx/error.log
unicornなら  /log/unicorn.log

AWSやVPSで普段環境構築してる人からしたら、ほとんど難しいところはないかと思います。
今後

GKE・k8sによるオートスケールの設定
Dockerによる環境開発改善・デプロイ改善
Cloud Storageによる外部ファイルサーバ利用

などをする際に、便利さを身にしみて感じるのではと思います。
では最後の投稿を楽しみにしていてください。

本番環境をGCP/AWSで何個か作り、インフラについて少しわかったこと【パフォーマンス改善編】

続きを読む

CloudFront + S3 でのディレクトリアクセス

前置き

S3にアップロードしたコンテンツをウェブ公開するときに、CloudFrontを噛ませるのはよくある構成です。S3をStatic Website Hostingで直接公開する場合とくらべて、

  • CDNの機能が使える
  • WAFが使える(CloudFrontにWAFをアタッチした場合)
  • 独自ドメインのhttps構成が使える(CloudFrontにssl証明書を付与)
  • アクセスはCloudFront経由に集約されるため、CloudFrontのアクセスログでアクセス状況を分析できる

といったメリットがあります。しかし、Static Website Hosting で使える index document が使えなくなるという問題が。

S3のindex documentとは

たとえば http://hoge.jp/fuga/ というふうにディレクトリに対するリクエストがきたときにどのファイルをレスポンスするか、を設定できる機能です。
公式ドキュメント

CloudFrontにもDefault Root Objectという似た機能がありますが、これはルートディレクトリに対するリクエストにのみ有効で、サブディレクトリへのアクセスには機能しません。

解決策として手っ取り早いのは、オリジンのS3のStatic Website Hostingを有効にすることです。
参考ページ

が、「S3バケットは直接パブリック公開したくない!」という場合にどうするか。

案1 Lambda@Edge

  1. CloudFrontにディレクトリアクセス用のBehaviorsを追加。
    既存Behaviorsと同じS3をオリジンに。PathPatternは /*/とすることで、サブディレクトリへのアクセスがこのBehaviorsに割り当てられます。既存Behaviorsよりも順位を上げておくのを忘れないように。
  2. 追加したBehaviorsにLambda@Edgeをアタッチ。
index.js
'use strict';

exports.handler = (event, context, callback) => {
    const request = event.Records[0].cf.request;
    request.uri = request.uri.replace(/\/$/i, '/index.html');
    callback(null, request);
};

サブディレクトリ(/*/)へのリクエストはLambda@Edgeによって/*/index.htmlへのリクエストに置き換えられます。
手順1は必須ではなく、既存のBehaviorsにLambda@Edgeをアタッチしてもいいんですが、その場合は全リクエストに対してLambda@EdgeがCallされてしまうので、ちょっと無駄かなと思います。ルートディレクトリへのアクセスはCloudFrontのDefault Root Objectの設定でカバーします。

案2 ダミーのS3バケットからリダイレクト

Lambda@Edgeがリリースされるまではこの案が第一候補だったかもしれませんが、今となってはLambda@Edge案一択でいいかも…。Lambda@Edgeのコストが許容できない場合や、ディレクトリ構成が単純な場合は引き続きこの案も有効かと思います。
  1. 空っぽのダミーS3バケットを作成。このバケットはStatic Website Hostingの設定を有効化。
  2. CloudFrontにディレクトリアクセス用のBehaviorsを追加。ダミーバケットをCloudFrontのオリジンに。PathPatternは /*/
  3. ダミーバケットにリダイレクトルールを設定。リダイレクトルールは正規表現が使えないので、リダイレクトさせたいサブディレクトリ名をすべて指定する必要があります。
    なお、この案はダミーで空っぽとはいえs3のパブリック公開が必要です。

続きを読む

ゼロから始めるLINEBot(AWS×node.js) ③投稿された画像をS3に保存

前回の記事の続きです。
ゼロから始めるLINEBot(AWS×node.js)②オウム返しする
https://qiita.com/tenn25/items/498f768a58ba6a6de156

この記事で分かること

  • LINEに投稿された画像をS3に保存する

この記事の対象者

  • いろいろと初心者(自分がそうなので)
  • MessagingAPIのような外部APIを使ったことがない
  • AWSもあんま詳しくない(アカウントは持ってるくらい)

流れ

第1回 とりあえず動かす
1.LINE Developersの登録/設定
2.実装(Lambda/node.js)
3.定期実行の設定(CloudWatch)

第2回 オウム返しの実装
4.ドメイン取得,DNS設定,SSL証明書の設定(Route53/ACM/SNS/S3)
5.イベントの受け取り(APIGateWay)
6.ユーザへの返信(Lambda/node.js)

第3回 ユーザ情報や投稿画像の保存(この記事)
7.ストレージ(S3やDB)との連携

投稿画像の取得

投稿からS3保存までの流れは以下の通りです。
①ユーザがLINEに画像を投稿する。
②画像は一旦LINEサーバに保存され、WebHookによって、APIGateWayが叩かれる。
③APIGateWayの裏でLambdaが実行される。
④Lambdaの処理でLINEサーバへ画像を取得する。
⑤Lambdaの処理でS3に画像を保存する。

S3への保存ファイル名は「ユーザID/現在時刻.jpg」としようと思います。

LINEにメッセージが投稿されると、WebHookを通じてLambdaにリクエストが届きますが、
リクエストのevent.events[0].message.typeがimageであれば画像が投稿されたと判断できます。

画像は一旦LINE側のサーバに保存されているようなので、
https://api.line.me/v2/bot/message/{messageId}/content
に対してGETリクエストを投げる必要があります。

GETリクエストの結果をdataとしてバッファに流し込めばS3に転送できそうです。

index.js

var https = require('https');
var AWS = require('aws-sdk');

AWS.config.update({
    accessKeyId: process.env.AWS_S3_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_S3_SECRET_ACCESS_KEY,
    region: process.env.AWS_S3_REGION
});

exports.handler = (event, context, callback) => {

    //リクエストを取得
    event = event.events[0];
    var replyToken = event.replyToken;
    var message = event.message;
    var userId = event.source.userId;


    if(message.type === 'text'){
        //テキストが投稿された場合の処理はこちら

    }else if(message.type === 'image'){
        //画像が投稿された場合の処理はこちら
        var s3 = new AWS.S3();
        var message_id = message.id;

        //投稿された画像はmessage_idによって取得
        var send_options = {
            host: 'api.line.me',
            path: '/v2/bot/message/'+ message_id +'/content',
            headers: {
                "Content-type": "application/json; charset=UTF-8",
                "Authorization": " Bearer " + process.env.CHANNEL_ACCESS_TOKEN
            },
            method:'GET'
        };

        var data = [];

        //LINEサーバに対してリクエストを投げると画像(data)が取得できるのでS3に保存する
        var reqImg = https.request(send_options, function(res){
            res.on('data', function(chunk){
              data.push(new Buffer(chunk));
            }).on('error', function(e){
              console.log("ERROR: " + e.stack);
            }).on('end', function(){
                //ここらへんはPromiseで書いた方がいいかもしれない

                //ファイル名として現在時刻を取得
                var nowDate = new Date();
                var nowTime = nowDate.getTime();

                var params = {
                    Bucket: process.env.S3_BUCKET_NAME, // ←バケット名
                    Key: userId + '/' + nowTime + '.jpg', // ←バケットに保存するファイル名
                    Body: Buffer.concat(data)
                };
                s3.putObject(params, function(err, data) {
                    context.done();
                });
            });
        });
        reqImg.end();
    }
};

node.jsの話になってしまいますが、
④Lambdaの処理でLINEサーバへ画像を取得する。
⑤Lambdaの処理でS3に画像を保存する。
このへんが非同期処理になってしまうため、
④が完了してから⑤が開始するようになんらか工夫する必要ああります。(上記の書き方かPromiseを使うなど)

続きを読む

AWS Elastic Beanstalk に Node-RED をデプロイしてみた 2。

ここ で、公式の手順でデプロイしたけど、ちょっと気になることを手直しした。

1. アプリケーション用のディレクトリ作成

mkdir ./nodered1201
cd ./nodered1201

2. package.json 作成

vim ./package.json

name キーの値を上記で作成したディレクトリと同名にしている。

package.json
{
  "name": "nodered1201",
  "version": "1.0.0",
  "description": "node-red demo app",
  "main": "",
  "scripts": {
    "start": "./node_modules/.bin/node-red -s ./settings.js"
  },
  "engines": {
    "node": "4.x"
  },
  "dependencies": {
    "node-red": "0.17.x",
    "serialport": "2.1.x",
    "aws-sdk": "2.4.x",
    "node-red-contrib-storage-s3": "0.0.x",
    "when": "3.7.x"
  },
  "author": "",
  "license": "ISC"
}

3. settings.js 作成

vim ./settings.js

adminAuth の部分は、 ここ を参照。
下記例では、参照先と同じように、ユーザが admin 、 パスワードが password としている。

settings.js
module.exports = {
    awsRegion: 'us-east-1',
    awsS3Appname: 'nodered1201',
    storageModule: require('node-red-contrib-storage-s3'),

    uiPort: process.env.PORT || 1880,
    mqttReconnectTime: 15000,
    serialReconnectTime: 15000,
    debugMaxLength: 1000,

    adminAuth: {
        type: "credentials",
        users: [{
            username: "admin",
            password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.",
            permissions: "*"
        }]
    },

    functionGlobalContext: {
    },

    logging: {
        console: {
            level: "info",
            metrics: false,
            audit: false
        }
    }
}

4. config の作成

4.1. ec2 インスタンスのポート設定

Node.js 環境で立ち上がるインスタンスの待ち受けポートが 8081 らしいので、そのポートを設定する。

mkdir ./.ebextensions
vim .ebextensions/ec2.config
ec2.config
option_settings:
  aws:elasticbeanstalk:environment:process:default:
    Port: '8081'

4.2. ec2 インスタンス内のディレクトリのパーミッション設定

vim .ebextensions/00_change_npm_permissions.config
00_change_npm_permissions.config
files:
  "/opt/elasticbeanstalk/hooks/appdeploy/post/00_set_tmp_permissions.sh":
    mode: "000755"
    owner: root
    group: root
    content: |
      #!/usr/bin/env bash
      chown -R nodejs:nodejs /tmp/.npm

下記は参考情報まで。

Node-RED で、ノードをインストールしようとすると失敗していたので、ログを見た。

npm-debug.log
verbose stack Error: EACCES: permission denied, mkdir '/tmp/.npm/node-red-contrib-aws-sdk'

確認すると、

drwxr-xr-x 329 root     root   12288 Dec  1 05:21 .npm

cfg:00_change_npm_permissions.config を作成して、あらためて確認すると下記のようになっていて、Node-RED でのノードのインストールもうまくいった。

drwxr-xr-x 337 nodejs   nodejs 12288 Dec  3 06:04 .npm

参考: Permission Error running npm install on elasticbeanstalk · Issue #4 · tomdale/fastboot-aws

5. eb コマンド実行

5.1. init で初期設定

eb init \
  --platform node.js \
  --region us-east-1

~/.aws/config が存在しないと、下記2つを聞かれるので、IAMユーザの認証情報で発行したものを、それぞれ入力する。

  • aws-access-id
  • aws-secret-key
You have not yet set up your credentials or your credentials are incorrect
You must provide your credentials.
(aws-access-id): 
(aws-secret-key): 
Application nodered1201 has been created.

5.2. create でアプリ作成

下記コマンド発行したらしばらくほっておく。
ロードバランサーは、 application じゃないとダメだと思う。Node-RED が Websocket を使っているから(←憶測あり)。
タイムアウトは、ごにょごにょ弄っている時に、タイムアウトしたので、ちょっと長めにセットした(無くてもたぶん大丈夫)。

eb create nodered1201-dev \
  --timeout 30 \
  --process \
  --elb-type application \
  --scale 1 \
  --instance_type t2.small

終わると、マネジメントコンソール上ではこんな表示が出る。

ebconsole.png

6. Node-RED にアクセスする

マネジメントコンソールで、Elastic Beanstalk を開きアプリの画面へ遷移すると、画面上部に下記のような URL が表示されているので、そのURLへアクセスする。

ebconsole2.png

Node-RED のログイン画面が表示されたら成功。 settins.js で設定した、 username と password を入力してログインする。

noderedlogin_c.png

フロー作ってデプロイしたり、ノードをインストールしてみたりする。

7. 作成された環境をチェック

7.1. EC2 をチェック

  • インスタンス

EC2_Management_Console.png

  • セキュリティグループ(下記は、キーペアを有効にした時のものなので、今回の手順だとポートは、 8081 のみになる。)

EC2_Management_Console3.png

  • ロードバランサー

EC2_Management_Console4.png

  • ターゲットグループ

EC2_Management_Console2.png

7.2. S3 をチェック

  • バケット

S3_Management_Console.png

8. 最後に

あとは、ドメインとSSLの設定だな。
直で Node-RED 使うだけならいらないけど、外からの package.json のデプロイは、試しとかなくては。(って前回も書いてるけど)

(追記)
いまさらだけど、今、目的とするものは、ロードバランシング不要、そして、ELB挟むことで、Node-REDの良さを潰してる。Network Load Blancer で MQTT 対応したとはいえ。
ELB 無しで、 をやろう。。

(追記2)
例えば、EC2インスタンスを削除した場合、インストールしたノードを無かった事にされる。ノードインストール時に更新される package.json をデプロイするか、S3に出す、運用にしないと、Beanstalk にする意味が薄れるのか。

続きを読む

サーバ、インフラ経験ゼロの初心者がAWSで「【非公式】Unity JP Mastodon」を作ってみた

Mastodon Advent Calendar 2017 3日目の記事になります。

【非公式】Unity JP Mastodonの管理人、荻野です。
logo.svg

今回はサーバー、インフラの実装経験ゼロの超初心者が、ネットに掲載されている情報のみで生まれて初めて触ったAWSを利用して、Mastodonインスタンスを組み立てたことを書いていきます。

サーバー、インフラ初心者故に引っかかった苦労点や、AWSに全く詳しくなかったために頭から地雷にダイブするなど、バカバカしいお話でも書いていきたいと思います。

ソースだけ読みたい方はこちらから見れます。

Mastodonインスタンスを立てる上で参考にさせていただいたサイト

一番お世話になったのが以下のサイトです。インスタンス作成時は、これをそのまま使わせていただきました。

マストドンAWS構築チュートリアル完全版|初心者から大規模運用まで

簡単な自動メンテナンス処理も記載されているのが本当にありがたかったです。
5月のゴールデンウィークに当時v1.3.2だったMastodonインスタンスを立てたのですが、ほぼこのサイトに書いてある手順をほぼそのままなぞったらMastodonインスタンスがAWSで動きました。

※現状のv2.0以上だと、一部ソフトウェアのバージョンをアップしないとビルドが通らないことがあります

他に参考にさせていただいたサイトをいくつか掲載します
AWSの無料SSLを使ってmastodonインスタンスを立てる手順
AWSでMastodonサーバー立てました chitose.moe
AWSのEC2で最小限の努力でmastodonを構築する
マストドンのインスタンスを構築するドン
Mastodon インスタンスを運用しているサーバを HTTP/2 対応させた話 (CentOS7 + nginx + OpenSSL)

AWS設定

Mastodonインスタンスを立てるにあたり、使用したAWSサービスは以下の通りです。

・EC2インスタンス
OS:ubuntu16.04
タイプ:t2.micro

インスタンス作成時のタイプ:t2.medium
※t2.microだとアセットの初回ビルドでメモリの上限を超えて落ちるため

・RDS
エンジン:PostgreSQL
クラス:db.t2.micro
ストレージ:20GB

・ElastiChache
タイプ:cache.t2.micro

・VPC
・S3
・CloudWatch
・CloudFront
・Route 53
・Certificate Manager

※ほぼ全て無料枠に抑えています。

無料枠でおさめられれば、費用はだいたい100円以下となっています。
画像をのぞいて、Unityリンク集やお知らせ表示の簡単なカスタマイズを入れています。

スクリーンショット 2017-12-02 21.25.28.png

現バージョン(v2.0以上)でMastodonインスタンスをAWSで動かしたい場合

Mastodonは日々高速で改良が加えられているため、2017年4月前に記載されたマストドンAWS構築チュートリアル完全版を見て新規にMastodonインスタンスを組み立てる場合、いくつか引っかかる点と当時未実装だった機能があります。

dockerのバージョンが1.13以上であることが必須

v1.4.1からdockerが1.13以上でないと、ビルドが通らないように変更されています。

現在、非公式のUnity Mastodonのバージョンアップ中
どうやら1.4.1はdockerが1.13以上でないとビルドできないようになっているっぽいな

— 荻野雄季@デジゲー博X-03ab (@YuukiOgino) 2017年6月14日

バージョンが1.13より低い場合、公式ドキュメントを見て、docker ceをインストールしましょう。

プッシュ通知(VAPIDキー)

プッシュ通知はv1.5.0から新規に追加されました。
そのため、プッシュ通知に対応する場合は以下の作業が追加されます。

1.以下のコマンドを叩いて秘密鍵/変数を生成します
RAILS_ENV=production bundle exec rake mastodon:webpush:generate_vapid_key
(ドッカーの場合、docker-compose run –rm web rake mastodon:webpush:generate_vapid_key)

2.生成した秘密鍵/変数を設定ファイルにセット
大抵の人が.env.productionと名付けるファイルで、以下の定数に生成した秘密鍵と変数をセットします。

VAPID_PRIVATE_KEY =
VAPID_PUBLIC_KEY =

生成さえすれば、どちらがどのKEYに対応しているかはわかると思います。

AWS(インフラ)初心者ゆえやらかした失敗

AWSは2017年5月のGWで初めて触ったため、いくつかバカバカしい失敗をしました。
自分への戒めとして、晒していきます。

RDSとElastiCacheを本番稼動用のデフォルト設定のまま本番公開した

はい、Mastodonインスタンス作成時に初めてAWSに触ったため、ろくに調べずに無知故にやらかした失敗です。

これ、何が失敗かというと、最大でも一日あたりのアクティブユーザーが50人以下のコンテンツなのに、明らかに超オーバースペックな上に壮大に無駄な費用が発生するモンスタースペックな構成に知らず知らずにしてしまったことです。

1.ElastiCache
ElastiCacheのデフォルトだとシミュレーションができなかったので、大雑把な計算をしてみました。

ノードのタイプ:cache.r4.large(メモリ12.3GB) 時間で$0.288の費用発生
レプリケーション数:2

大雑把に料金を計算すると$0.288×2×24×31=$428.544(約48,083円)
明らかに不必要な投資です。

2.RDS
こちらは実際のデフォルト設定からある程度シミュレーションで計算ができました。
スクリーンショット 2017-12-02 19.07.27.png

なんと驚愕の月額$1386.07(約155,521円)!
明らかに過剰投資です。

これは現在の料金設定からの計算であり、当時は1日1万ずつ自動課金されるぐらいの消費をしていました。
構築時は設定した時間が3〜4時間程度な上、請求の欄を見ていなかったために異常な料金の上がり方に気づいていませんでした。

リリースしてから1日後、請求額を見たら明らかに想定と違う料金の上がり方をしていることに気づき、料金を調査して発覚しました。これをどうにかしないと毎月20万消える恐ろしいモノになるため、早期に構成を変更せざるを得ませんでした。

この時に初めてAWSの仕様として、容量は簡単に上げることは可能だが、容量を減らすことはできないことを知り、改めてインスタンスを作り直さないといけないことになりました。

ElastiCacheの再設定

Mastodonはサーバーキャッシュは全て捨てても、直近で影響が出るのがホームのページの表示ぐらいというのが検証してわかったため、完全に付け替える方針にしました。
超小規模であればレプリケーション数は1、メモリ0.5GBでも十分と判断し、ElastiCacheは無料枠に収めるインスタンスを改めて作成し、付け替えました。

無事に接続を確認後、金食い虫のインスタンスは即刻削除して料金を発生しないようにしました。

RDSの再設定

一番困ったのがDB、RDSの再構築です。一度世間に解放してしまった以上、当時90人弱のユーザー登録があったので破棄してしまうと再度登録させるという非常に面倒な手間を強いることになるため、DBをそのまま移植しないといけません。

仕事では基本的にSQLでの流し込みぐらいしかしてなかったため、時間が過ぎるごとに課金額があがる恐怖と戦いながらネット上でRDSのバックアップと復旧方法を検索しました。

最終的に以下の方法でDBの移植を行いました。

1.dumpファイル作成

まずdumpファイルを作成します。叩いたコマンドは以下の通りです。

psql -f [作成するdumpファイル名] --host=[RDSエンドポイント]  --port=[ポート] --username=[ユーザー名] --password --dbname=[DB名]

2.dumpから復旧

無料枠で収まるRDSインスタンスを作成後、以下のコマンドを実行しました。

sudo pg_restore -c -h [RDSエンドポイント] -U [ユーザ名] -d [DB名] [dumpファイル] 

詳しくはPostgreSQL 9.2.4文書をご覧ください。

あとは新規に作成したRDSに接続するように設定を変更し、無事トゥート一覧やユーザーリストが表示されていることを確認後、古いインスタンスを即時削除しました。

これでなんとかDBの復旧ができました。
頭ではDBはバックアップとって復旧する、ということをやらなければいけないとはわかってましたが、dumpファイルからの復旧は初めての作業だったため、非常にテンパった覚えがあります。

久しぶりに頭をフル回転させましたw

S3をダイレクトに接続していた結果、通信プロトコルがHTTP1.1形式でページの表示が遅かった

これもやらかしの一つです。ドキュメントを読めば明らかなのですが、S3の通信プロトコルはHTTP/1.1のため、せっかくHTTPSで接続しているのにHTTP/2.0にしていないために非常に無駄な通信コストが発生していました。

CloudFrontを経由すればS3でもHTTP/2.0対応ができることを知ったため、早速試してみました。

1.ドメイン&証明書発行

S3用のドメインと証明書を発行する必要があるため、AWSの証明書で作成します。
mastodonインスタンス作成時と同様、証明書のリクエストを行います。このドメインさえ確保できれば、あとはCloudFrontでインスタンスを作成するのみです。
スクリーンショット 2017-12-02 20.41.29.png

2.CloudFrontインスタンス作成

まずインスタンスを作成します。上のWebから作成します。
スクリーンショット 2017-12-02 20.34.50.png

Create Distribution画面が出てくるので、Origin Domain NameにS3のバケットをセットします。

スクリーンショット 2017-12-02 20.35.23.png

続いて、SSL CertificateでCustom SSL Certificateを選択し、先ほど発行したドメインをセットします。

スクリーンショット 2017-12-02 20.36.40.png

最後、HTTP VersionsがHTTP/2をサポートする設定になってるか確認し、インスタンスを作成します。
スクリーンショット 2017-12-02 20.35.59.png

最後、このドメインをS3_BUCKETに設定すれば、HTTP/2.0対応完了となります。
HTTP/2.0に変更後、画像等のアセットダウンロードが体感速度的に2倍ぐらい早くなった気がします。

参考に、画像がたくさん上がってくるPawooのインスタンスユーザーを表示した上での速度です。

スクリーンショット 2017-12-02 20.58.30.png

※すみません、データ取る前に切り替えてしまったので、変更前の具体的な数値が出せないです

33の画像を読み込んだ上での速度です

一見わかりにくくて申し訳ないですが、これはAWSで全て無料枠で収めてこの速度が出るということなので、EC2やElastiCacheの性能をあげればもっと早くなると思います。

Mastodonインスタンスを組み立てる上で役だったスキル

私は一応、会社で某ソーシャルゲームのWebフロントエンジニアのため、会社で(嫌でも)以下の経験をしたのがMastodonを立てる時に役立ちました。

・簡単なLinuxコマンドとコマンドライン

大体本番で使われているWebサーバーはLinuxのCUIベースだったので、フロントのソースをアップするために何回かviを編集していたこと、シンクコマンドを叩いていたのが役に立ちました。(GUIばっかり触ってると、CUIだとわかりにくい部分があるため)
Linuxでサーバーを立てる場合、ある程度Linuxコマンドを叩いて慣れておいたほうがいいと思います。

MastodonとAWSを触ったり、地雷を踏んだりして得られたスキル

最後のまとめです。
インスタンス作成時はAWSを触って4日、色々と失敗をやらかして得られた経験は以下の通りです。

・RDS(PostgreSQL)でのバックアップ復旧作業
・AWSにおけるサーバー環境構築
・Mastodonインスタンス構築の知見
・サーバーコストへの意識
・ユーザーがいる状況での本番環境のバージョンを更新するという恐怖と覚悟

とくによかったと思えるのはサーバーコストへの意識です。
実際に立てたからわかることですが、サーバーコストを適切にしないと無駄な費用が発生して回らなくなるというのをよく理解しました。
実質、フロント部分しか触ってないと、会社環境によってはこの辺りの意識がないところもあるので、改めてサーバーコストの意識が高まったのはやってよかったと思います。

本番環境の更新は毎回冷や汗を書きます。なぜなら会社では万が一が起きても頼れる人がいますが、このインスタンスで頼れるのは自分一人です。万が一が発生した場合、ネット上で情報がなければお手上げです。
謎のエラーでバージョンが更新できずに起動できない(だいたいコンフリクト放置が原因)ときは本当にあせります。

かなり必死で頭を回してるからこそ、動いた時に得られる経験値というのは相当なものだと感じています。
もし、Web系の会社に行きたいと考えている人は、mastodonインスタンスを立てて運営してみると、色々と実践的な経験を短期間で詰めるかもしれません。

明日はxserverさんによる「インスタンス運用アンチパターン」です。楽しみです!!

続きを読む

AWS ELB を使って冗長構成を組むまでの気にするポイント

今回ELBを使って、EC2の冗長構成を作りました。
その中で、何個かはまりごとにも出くわしたので、メモがわりに投稿します。

前提条件の構成

(BEFORE)

インターネット -> EC2 -> RDS

(とりあえずのAFTER)

インターネット -> ELB -> EC2 -> RDS

(さらにAFTER)

インターネット -> ELB -> EC2その1 / EC2その2 -> RDS

大まかな流れ

  1. ロードバランサーの作成
  2. ターゲットグループの作成
  3. ヘルスチェック用ファイルの設置
  4. DNSのAレコード登録
  5. EC2インスタンスを新規作成
  6. DNSのAレコード登録(直アクセス確認用)
  7. ターゲットグループに新規EC2インスタンスを登録
  8. 動作確認

手順とハマりポイント

1. ロードバランサーの作成

  • 入口が見つけづらいので数分ハマるので気をつけてくださいw

EC2_Management_Console.jpg

  • ロードバランサーの種類を選びます。今回は「Application Load Balancer」を選択。

EC2_Management_Console.jpg

  • AWSコンソールの手順に従い、設定を行います。
1. ロードバランサーの設定
2. セキュリティ設定の構成
3. セキュリティグループの設定
4. ルーティングの設定
5. ターゲットの登録
6. 確認

2. ターゲットグループの作成

  • ターゲットグループには、複数のAZを指定し冗長構成を図りました。
  • ターゲットは後からでも登録できるので、後回し。
  • 基本的には、ここでどんな設定をしても、DNSのAレコードを設定しない限り影響ないので、気軽に

3. ヘルスチェック用ファイルの設置

  • 確認をしている中でヘルスチェックが「Unhealthy」と出ていたので、Document Root配下にチェック用ファイルを置き、ヘルスチェックを再設定

4. DNSのAレコード登録

  • ロードバランサーの「説明」タブにある情報を参照し、DNSのAレコードを追加

EC2_Management_Console.jpg

  • この状態で、ブラウザからアクセスすると (とりあえずのAFTER) の構成になっている

5. EC2インスタンスを新規作成

  • 既存のサーバのAMIを作って、いつも通りにコピー作成
  • その時に、AZを既存のEC2と違うところで作るのを忘れずに。あとで必要になります。

6. DNSのAレコード登録(直アクセス確認用)

  • 既存のサーバ+新規作成サーバのパブリックIPを参照し、DNSのAレコードに追加
  • ロードバランサーを経由せず、インターネットから直接EC2にもアクセス可能な構成とするため

7. ターゲットグループに新規EC2インスタンスを登録

  • 既存のサーバ+新規作成サーバのパブリックIPを参照し、DNSのAレコードに追加
  • ターゲットグループのターゲットの種類を「ip」で選択した場合は プライベートIP を指定する必要があります。ここで パブリックIP を指定しようとし、しばらくハマりました。

8. 動作確認

  • 晴れて、ロードバランサーの設定は完了です。
  • それぞれのサーバにアクセスがきていること、ログが出ていること、ラウンドロビンできていることを確認し、ひと段落。

ここには書いてないけど、一緒にやったこと

  • ログファイルバックアップの設定

    • ログファイルをS3にバックアップしているのですが、単純に稼働サーバを増やすだけだとログファイル名が被っちゃうことが想定されたので、ログファイル名にホスト名を含めるなどの手入れをしました。
  • ログに出てくるIPアドレス取得に X_HTTP_FORWARDED_FORに切り替え

感想

サブネットグループやインスタンスの新規作成など、結構直感で運用していた面も改めてレビューすることができました。
かといって、大きなハマりポイントはなく、設定完了まで行けたのでAWSさすが!!という感じです。

続きを読む

既存の ELB に SSL 証明書 を追加しようとするとエラーが起きる。

スクリーンショット_2017-10-14_17_08_01__1_.jpg

AWSサポートに質問をしました。

画像にありますように、既存の ELB に SSL 証明書を追加しようとすると Server Certificate not found for the key というエラーが発生します。
こちらの解消方法をご教授いただけないでしょうか?
よろしくお願いいたします。

回答はこちら

お問い合わせ頂き誠にありがとうございます。

画像にありますように、既存の ELB に SSL 証明書を追加しようとすると Server Certificate not found for the key というエラーが発生します。

マネジメントコンソール(以下MC)から ELB に SSL 証明書をアップロード・変更する場合、保存時にご連絡いただきましたエラーが発生することがございます。

証明書自体に問題がない場合、本件は MC の既知の不具合によって発生してしまったと考えられます。
こちらの不具合につきまして修正予定となっているものの、修正が完了する日時につきましては未定となっております。
ご迷惑をおかけいたしまして恐れ入りますが、修正されるまでの間に再度発生してしまった場合には、今回の方法や後述いたします AWS CLI でのご対応をお願いいたします。

上記のエラーが発生した場合、実際には証明書のアップロードまでは成功している場合があります。
その場合にはMCで証明書の変更を再度お試しいただきますと、対象の証明書が選択可能になり、その場合には更新も成功します。

なお上記エラーメッセージが発生した場合の別のワークアラウンドとしては、AWS CLI にて回避していただく方法をお勧めしております。

AWS CLI を使用した SSL 証明書の置き換え
http://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/classic/elb-update-ssl-cert.html#us-update-lb-SSLcert-cli

その後

何回かトライしてから見にいってみると証明書は登録されていた状態になっていたので、アップロードは成功していました。
かろうじて、画面上からできたのでよかったですが、同じところにハマる人がいた時のためにメモです。

続きを読む