投稿者「kanazawa」のアーカイブ

AWS

RDS for MySQLからAuroraへ移行しました

kanazawa|2016年01月29日
Aurora

インフラエンジニアの金澤です。

ランサーズでは、今月、DBMSをRDS for MySQLからAuroraに移行しました。
その内容について、2016/1/28に行われました「ヒカラボ MySQL勉強会」 で発表させていただきました。

Auroraの仕様詳細や、一般的なパフォーマンス測定の結果については、既に良い資料が発表されておりますので、今回の発表内容は「ランサーズで移行した結果どうだったのか?」を中心に発表させていただきました。

今回、ランサーズでAuroraに移行した目的は、主に以下の4点になります。

  1. パフォーマンスの向上
  2. メンテナンスなしでのカラム追加(Replica Lagの改善)
  3. TV対策(リードレプリカの上限が5台→15台に増加)
  4. 費用削減(MultiAZ分のインスタンス費用削減)

結果、パフォーマンスの向上以外については目的を達成できました。

パフォーマンスにつきましては、今後も継続的にウォッチし、改善できましたら再度報告させていただきたいと思います。

開発環境のDocker化

kanazawa|2015年12月10日
AWS

インフラエンジニアの金澤です。

今回、ランサーズの開発環境をDocker化しましたので、その内容を紹介いたします。

Docker移行の決断

Dockerについては、ここ1年で技術的な調査はしていましたが、決定的なメリットを見出していたわけではなく、採用に踏み切ってはいませんでした。

その一方で、開発規模は拡大し続けており、ランサーズ本体に加え、関連サービスのプロジェクトも並行して進むようになり、開発環境のリソース不足に悩まされるようになってきていました。

ランサーズの開発環境は、主に以下の構成で運用されています。

  • 個人PC
    • Virtualbox + Vagrant でCentOS環境を構築
    • Ansibleでプロビジョニング
  • 社内VMサーバー
    • VMWare 上にCentOS環境を構築
  • AWS検証用環境
    • EC2(CentOS、Amazon Linux)上に構築
    • Ansibleでプロビジョニング

個人PCの開発環境は、VirtualBoxにメモリ2GB、HDD 50GBのVMが稼働しています。
このVM上にランサーズが稼働しているわけですが、関連サービス(例えば、チャットサービス等)のプロジェクトが立ち上がると、その開発環境も必要になります。

関連サービスはランサーズと連動して動作しますが、ミドルウェア構成が異なるため、1つのVMに共存させるにはいろいろ不都合があります。
そのため、別VMを構築することになるのですが、個人PCに2台以上のVMを稼働させると、HDD、メモリ等のリソースが不足し始めてきます。

今回の開発環境のDocker移行は、差し迫った問題である、個人PCのリソース解決手段として導入しました。

Docker移行の方針

開発環境のDocker化は、以下の方針で行ないました。

  • 個人PC上にはDockerMachine(DockerToolbox)、VMWareにはCoreOSで構築する
  • 本番AWS環境のDocker化はしない
  • 極力、本番AWS環境と同じ構成にする
  • 個人でプロビジョニングはしない(コンテナを持ってくるだけ)
  • 開発環境、本番環境共に(ほぼ)同じコードでプロビジョニングできるようにする
  • Vagrant時代の利便性を維持する(共有ディレクトリ、XDebugによるステップ実行など)

「個人でプロビジョニングはしない」方針にしたのは、構築に時間がかかるためです。

Vagrant + Ansibleでプロビジョニングしていたときは、構築(特にMySQLデータのインポート)に時間がかかり、完了まで数時間ほどかかっていました。

そのため、積極的に再構築する人が減り、Ansible Playbookの更新が滞った結果、プロビジョニングエラーが増え、それを補うための手作業が増える悪循環に陥っていました。

また、Windowsの場合、Vagrant、Ansibleのバージョンなどによる相性がシビアで、構築がなかなか成功しないという悩みもありました。

そのため、Docker移行後は、コンテナのプロビジョニングは特定の人が行い、Dockerを使う人は、完成したコンテナをpullすれば構築できる手順にしました。

これらの方針で進めたため、一部、Dockerのベストプラクティスに従っていない方法も採用しています。

例えば、以下のような方法です。

  • Dockerfileだけでなく、Ansibleでもプロビジョニング
  • 1コンテナに2つ以上のサービスを稼働
  • sshdの稼働
  • root以外の一般ユーザーの作成
  • 固定IPの付与
  • MySQLにデータをインポートしたコンテナをそのままRegistryに登録

Dockerのベストプラクティスを実現させるには、リリース方法も踏まえた上で本番環境の構成にも手を入れる必要があります。
今回の目的は開発環境のリソース不足の解決がメインのため、その目的の達成に注力し、上記のような方法も採用しています。

開発エンジニアには、事前に以下のようなイメージを共有してから着手しました。


docker02

Dockerを使った開発環境構成2

 

移行時に直面した問題とその解決方法

コンテナのプロビジョニング

コンテナのプロビジョニングは、主にDockerfileで行いますが、本番環境のEC2で稼働するサービスのコンテナには、共通のAnsibleコードを利用してプロビジョニングします。

AWSのいわゆるマネージド系のサービス(ELB、RDS、Erasticache等)にあたるコンテナはDockerfileのみで構築、EC2で構築するサービスは、Dockerfileでベースを作成した上で、SSH経由でAnsibleのPlaybookを実行しています。

※docker-machineは再起動すると個人で作業したファイルが消えてしまうため、Playbookの実行用にAnsibleをインストールしたコンテナを別途用意しています。

Docker Registory

コンテナを格納するRegistryには、DockerHubなどのサービスがありますが、現状はどのサービスも有料で、サーバーも海外にあるものがほとんどです。

データをインポートしたMySQLのコンテナは数GBになるため、転送速度が遅いと運用に大きな影響を与えます。

そのため、プライベートレジストリを構築し、コンテナはAWS東京リージョンのS3に格納することにしました。

プライベートレジストリ用にサーバーを用意する選択肢もありましたが、Docker Registoryのコンテナが公開されていますので、それを各Docker Machine上で起動しています。

※コンテナをpull、pushするときのみ起動すればよいので、リソース的にはほとんど問題になりません。

Docker Registoryのコンテナ起動時に、以下のオプションを指定することで、格納先をS3に設定しています。

docker run -d \
    --name registry \
    -p 5000:5000 \
    -e REGISTRY_STORAGE_S3_ACCESSKEY=XXXXXXXX \
    -e REGISTRY_STORAGE_S3_SECRETKEY=xxxxxxxx \
    -e REGISTRY_STORAGE_S3_BUCKET=docker-registory-lancers \
    -e REGISTRY_STORAGE_S3_REGION=ap-northeast-1 \
    -e REGISTRY_STORAGE_S3_ROOTDIRECTORY=/ \
    -e REGISTRY_STORAGE=s3 \
registry:2.2

複数サービスの起動

Dockerは、1コンテナ1サービス、サービスの起動はフォアグラウンドが原則ですが、本番環境と同様の構成にするため、sshdを含め、サービスを複数起動しています。

複数サービスを起動するための手法については、様々な方法が紹介されていますが、以下のようなスクリプトで稼働させるのが最もわかりやすく、この方法を採用させていただきました。

service.sh
#!/bin/sh
/sbin/service sshd start
/sbin/service httpd start
 
while true
do
 sleep 10
done

Dockerfileに、起動時に上記のスクリプトを実行するように記述します。

Dockerfile
# service.shをコンテナに配置
COPY ./service.sh /root/
RUN chmod 744 /root/service.sh
 
# 起動時にservice.shを実行
CMD ["/root/service.sh"]

固定IPアドレスの付与

CentOS6の場合は、ip addr add コマンドで固定IPアドレスを付与できます。
docker run、docker start後に、以下のようにdocker execコマンドで付与しています。

DOCKER_ID=$(docker inspect --format="{{.Id}}" $DOCKER_NAME)
IP_STATIC=172.17.51.11
NIC=eth0
docker exec $DOCKER_ID ip addr add $IP_STATIC/16 dev $NIC

※つまり、コンテナ起動時に付与されるIPアドレスと、固定IPアドレスが2つ付与されている状態になります。

既存のIPアドレスの削除も試みましたが、iptablesの設定も必要で手順が煩雑になるためやめました。IPアドレスが衝突する可能性が僅かながらあるのですが、今は割り切って利用しています。

ELBコンテナ

AWSのELBに相当するコンテナを用意しています。

本番AWS環境では、ELB経由でAppサーバーにアクセスしています。
SSL Termination機能を利用しているため、Appサーバー内ではhttpしか処理していません。

その状態をシミュレートするため、ELBコンテナでhttps→http変換を行い、環境変数X-Fowarded-Protoをセットした上で、Appサーバーに処理を渡すように設定しています。

具体的には、以下のようなVirturlhostの設定を行っています。

ProxyAddHeaders Off

<VirtualHost *:80>
    ServerName dev.lancers.jp
    ServerAlias *.lancers.jp
    RequestHeader set X-Forwarded-Proto "http"
    RewriteEngine On
    RewriteCond %{SERVER_NAME} ^(.*).lancers.jp$
    RewriteRule ^/(.*) http://%1.lancers.jp/$1 [P,L,QSA]
</VirtualHost>
<VirtualHost *:443>
    ServerName dev.lancers.jp
    ServerAlias *.lancers.jp
    ErrorLog logs/ssl_error_log
    TransferLog logs/ssl_access_log
    LogLevel warn
    SSLEngine on
    SSLProtocol all -SSLv2
    SSLCipherSuite ALL:!ADH:!EXPORT:!SSLv2:RC4+RSA:+HIGH:+MEDIUM:+LOW
    SSLCertificateFile /etc/pki/tls/certs/lancers.jp.cert
    SSLCertificateKeyFile /etc/pki/tls/private/lancers.jp.key
    RequestHeader set X-Forwarded-Proto "https"
    RewriteEngine On
    RewriteCond %{SERVER_NAME} ^(.*).lancers.jp$
    RewriteRule ^/(.*) http://%1.lancers.jp/$1 [P,L,QSA]
</VirtualHost>

また、このELBコンテナは、lancers.jpドメイン以外のサービスも起動した場合に、同じIPアドレスのdocker-machineを利用するための、リバースプロキシの役目も果たしています。

共有ディレクトリ

docker-machineのVMを作成後、VirtualBoxの設定で以下のように共有フォルダーを設定することで、docker-machineのディレクトリとPC上のディレクトリを共有できます。

vb_setting_shared_folderさらに、docker run時に-vオプションを指定することでコンテナとdocker-machineのディレクトリを共有します。

これで、PC上で直接ソースコードの修正が可能となります。

以下は、コンテナの/var/wwwを共有した場合のコンテナ起動例です。

docker run \
--name $DOCKER_NAME \
-v /Users/www:/var/www \
--hostname $DOCKER_NAME \
--privileged \
-d $IMAGE_NAME

※ディレクトリの所有権の関係で、/var/www以下での書き込みは、root権限が必要になる場合があります。

hostsの設定

Dockerfileで/etc/hostsを編集してもコンテナには反映されません。
docker run時に–add-hostオプションを指定することで、hostsを設定できます。

docker run \
--name $DOCKER_NAME \
--hostname $DOCKER_NAME \
--add-host=dev.lancers.jp:172.17.50.11 \
--add-host=dev-img.lancers.jp:172.17.50.11 \
--privileged \
-d $IMAGE_NAME

ポートマッピング

ポートマッピングは、コンテナで起動したhttpのサービスをPC上で確認するために必ず設定することになりますが、MySQLコンテナにも設定しています。

以下のように設定することで、PC上からdocker-macineのIPアドレスで、直接MySQLコンテナのMySQLにアクセスできます。

Dockerfile
EXPOSE 3306

起動時に、-pオプションでポートをマッピング

IMAGE_NAME=mysql:latest
DOCKER_NAME=mysql-51-11
IP_STATIC=172.17.51.11
PORT=3306
 
docker run \
--name $DOCKER_NAME \
--hostname $DOCKER_NAME \
-p $PORT:$PORT \
--privileged \
-d $IMAGE_NAME

Xdebugによるステップ実行

Xdebugをインストールし、php.iniに以下の設定をすることで、PHP Storm等でステップ実行を可能にしています。
(※ステップ実行時に、xdebug.remote_autostart = Onにしてhttpdを再起動します)

php.ini

[xdebug]
zend_extension="/usr/lib64/php/modules/xdebug.so"
xdebug.remote_enable = On
xdebug.remote_port = 9000
;xdebug.remote_autostart = On
xdebug.remote_host = 192.168.99.1
xdebug.profiler_output_dir = "/tmp"
xdebug.max_nesting_level= 1000
xdebug.idekey = "PHPSTORM"

Docker化の効果

開発環境をDockerに移行したことで、複数のVMを起動する必要がなくなり、PCリソースの削減になったことはもちろんですが、加えて、短時間でかつ簡単に構築できるようにもなりました。

エンジニア以外の方も積極的に開発環境を構築するようになり、その結果、元エンジニアのディレクターが開発に復帰したり、子供が産まれたばかりのデザイナーも、リモートワークで子供の面倒を見ながら仕事を進めることができるようになりました。

また、データ変更や、インフラの構成変更時は、コンテナを破棄、更新するだけで良くなりました。

今後、ランサーへの開発依頼がよりスムーズに進むことも期待しています。

今後の展望

一定の効果はありましたが、いわゆるDockerのベストプラクティスにはまだ程遠い状態です。

今回のDocker移行で、エンジニアのDocker意識が高まりましたので、今後、マイクロサービス化も見据えながら、Docker導入をさらに進めていきたいと思います。

AWSのスポットインスタンス運用

kanazawa|2014年11月12日
AWS

はじめまして。ランサーズインフラエンジニアの金澤です。
AWSのスポットインスタンスを利用したことはありますか?
スポットインスタンスとは、AWSの余剰インスタンスを入札制で提供する仕組みです。
各インスタンスの1時間あたりの料金比較しても、スポットインスタンスは、(リアルタイムな相場の変動はありますが) オンデマンドインスタンスの1/10近い価格で提供されています。

Amazon EC2 料金表

http://aws.amazon.com/jp/ec2/pricing/

1 時間当たりの料金:アジアパシフィック(東京)Linuxの場合
(2014/11/13現在)

インスタンスクラス オンデマンド スポット 参考:リザーブド
(重度1年間)
t1.micro $0.026 $0.0031 $0.016
t2.micro $0.020 $0.005
m3.medium $0.101 $0.0115 $0.030
c3.large $0.128 $0.0212 $0.047
r3.large $0.210 $0.0265 $0.048
i2.xlarge $1.001 $0.228

 

デメリットは、以下の条件で強制的にTerminateされてしまうことです。

  • 入札価格よりスポット価格が高くなる
  • スポットインスタンスの在庫がなくなる

スポットインスタンスを利用する際は、Terminateのリスクを織り込んだ上で運用する必要があります。
ランサーズでは、検証用AWS環境をスポットインスタンスで運用しており、今回は、その手順、および運用方法を紹介いたします。

スポットインスタンスの起動方法

EC2インスタンスの作成時、Step3のPurchasing optionで「Request Spot Instances」をチェックすると、スポットインスタンス関連の項目が追加表示されます。

image2014-11-9 20-25-54

「Current price」が現在のスポット価格です。
スポットインスタンス稼働時に、この金額を超えるとTerminateされてしまいます。

Maximum priceの設定

「Maximum price」には、スポットインスタンス運用時に「最大で支払える金額」を指定できます。
インスタンスを起動する前に、過去のスポット価格の推移を調べておきましょう。
「Spot Requests」メニューから「Pricing History」を押すと確認できます。
image2014-11-9 22-40-18「Maximum price」を高く設定するほどTerminateされにくくなるわけですが、残念ながら設定できる値には上限値があります。

※上限値を超えて設定しようとすると、以下のメッセージが出力されます。

SpotRequestFailed

Maximum priceの上限値

上限値は、インスタンスクラスごとに変動します。
※調べた限りでは、以下の値が上限値のようです。

インスタンスクラス 上限値
t1.micro $0.108
m1.small $0.352
m1.medium $0.700
m1.large $1.400
m1.xlarge $2.800
m3.medium $0.684
m3.large $1.368
m3.xlarge $2.736
m3.2xlarge $5.472
c3.large $0.768
c3.xlarge $1.532
c3.2xlarge $3.064
c3.4xlarge $6.128
c3.8xlarge $12.256
cc2.8xlarge $2.960
m2.xlarge $2.020
m2.2xlarge $4.040
m2.4xlarge $8.080
hi1.4xlarge $0.1601

 

プライベートIPアドレスの指定(Webでは不可)

また、現時点では、Webコンソールからは固定プライベートIPアドレスを設定することができないようです。

「Request Spot instance」チェック前。
「Network interfaces」 でIPアドレスが設定可能です。

image2014-11-9 22-57-15
※「Request Spot instance」をチェックすると「Network interfaces」が消えてしまいます。

image2014-10-3 23-44-34

プライベートIPを指定してスポットインスタンスを作成する

Webコンソールからはできませんでしたが、AWS CLIを利用すれば、プライベートIPアドレスを指定してインスタンスを起動することが可能です。

AWS CLIによるスポットインスタンスのリクエスト

aws ec2 request-spot-instances \
  --spot-price “<入札価格>" \
  --instance-count 1 \
  --type "one-time" \
  --launch-specification "{ \
    \"ImageId\":\"\", \
    \"InstanceType\":\"<インスタンスタイプ>\", \
    \"NetworkInterfaces\":[{\"DeviceIndex\":0, \
    \"SubnetId\":\"<起動先サブネット>\",\
    \"PrivateIpAddresses\":[{\"PrivateIpAddress\":\"<プライベートIP>\", \"Primary\":\"true\"}] }], \
    \"KeyName\":\"<SSH鍵ペア名>\"
}"

スポットインスタンスのリクエストにあたっては、以下の点に注意が必要です。

  • リクエスト内にインスタンス名を含めることができない
  • Security Groupの設定とネットワークインターフェース設定(パブリックIP付与など)を同時に行うことができない

セキュリティグループはdefaultが割り当てられますので、起動後に適切なものに変更してください。

スポットインスタンス起動コマンドのオプションについて

オプション 説明
–instance-count 作成するインスタンス数を指定します。
※プライベートIPアドレスを指定して作成するので、1を入力します。
–launch-specifiation 起動するインスタンスの詳細設定をJSON形式で指定します。
–spot-price 入札価格をUSDで指定します。
–type “one-time”と”persistent”の2種類があります”one-time”では一回スポットインスタンスを起動したらリクエストが失効するのに対し、”persistent”を指定した場合はスポットインスタンス起動後も任意で入札をキャンセルするまでリクエストが維持されます。

 

その他のオプションにつきましては、以下のURLをご確認ください。
http://docs.aws.amazon.com/cli/latest/reference/ec2/request-spot-instances.html

スポットインスタンスの日次バックアップ

スポットで稼働中のEC2インスタンスについては、AMIを毎日取得し、Terminateされた場合は、このAMIから再度リクエストしています。
(※最近は発生してませんが、2014/4月~2014/6月頃は、在庫切れによるTerminateが度々発生していました。)

ランサーズでは、サーバーワークス社のCloudAutomatorを利用しています。
http://cloudautomator.com/

image2014-10-3 23-40-0

無償での利用には条件がありますが、バックアップの世代数が管理でき、バックアップ以外にも便利な機能がいくつもありますので、重宝しております。

最後に

スポットインスタンスは、Terminateのリスクがあるものの、適用領域とリスクコントロールを工夫すれば非常に安価に運用することができます。