はじめに
本番環境がAWSにあるのですが、開発環境が微妙に違う状態だったので本番同等の環境(=ステージング環境)を作ろうということになりました。
ちょうど手が空いていた私が担当になったのですが、A-SaaSシステムはマイクロサービスアーキテクチャでサーバ台数は数十台以上、しかも自分は週3日の契約で会社にジョインして半年も経っていない状態というところからどこまでできたか、この数ヶ月の記録です。
作りたいもの
- サーバやネットワークの構成が本番と同じ
- データは企業会計を扱うセンシティブなデータなので本番と別データ
- 本番同等のサーバ構成を1セットとして手軽にいくつも作ったり削除したりできる
やり方
AWSコンソールを眺めたりサーバを調査したりして検討し、下記の流れで本番環境をコピーしました。
1. ステージング環境用のAWSアカウントを作成する
最初は本番のアカウントと同じアカウント内に作成しようと思ったのですが、作業を少し始めてみてこれは危険すぎると気づいて別アカウントにしました。今後の運用を含めるとIAMの権限管理をよほど厳密しないとオペミスが起こるだろうということでアカウントを分けています。
2. インスタンスはAMI化してステージングアカウントに共有する
「本番そのまま」という要件を満たすために、確実な方法としてサーバをそのまま持ってくることにしました。
イメージ化をし、アカウント間で共有することでAMIを利用できます。
3. CloudFormerで本番のネットワークやサーバ構成のCloudFormationのテンプレートを作成する
CloudFormerを利用すると、ネットワーク構成やRDSやEC2の設定を持ってこれます。細かいところではRDSのパラメータグループは一部はコピーされなかったので同一になっているかチェックする必要がありました。
4. AMIから作成したインスタンスを各環境用に変更する操作はAnsibleでセットアップする
ホスト名やLDAPの設定など行う必要がありますが、それらはAnsibleでセットアップすることで作業内容が全て可視化できました。
実際の作業では、本番環境への影響がでることを避けるため、下記を確実に行いました。
- AWSやGCPのキー情報は削除する
- 本番IPへの接続はセキュリティグループのOutboundトラフィックの制限でそもそもアクセス出来ないようにする
その他
ネットワーク構成の概念図は下記のとおりです。
LDAPや管理ツールなどを置いておくVPCを別立てで用意して、ステージング環境とはVPC Peeringで結びます。
間違って本番環境を操作するようなミスを防ぐため
- マシンのホスト名は環境ごとに変える
- IPの範囲も変える
などは意識して設計しています。
工夫したところ
CloudFormation
CloudFormerで作成したところ数千行のJSONが出てきたので、複数ファイルへと分割しました。
依存関係が出てくるので、構築パラメータとして入力するようにしたり、
Parameters:
BaseStackName:
Type: String
Description: Base Stack Name
(略)
Resources:
Type: AWS::EC2::Instance
Properties:
(略)
Tags:
- Key: Name
- Value: !Join
- '-'
- - !Ref BaseStackName
- serverA
Fn::ImportValueを使います。
Outputs:
SomeValue:
Description: A sample value
Value: !Ref SomeInstance
Export:
Name: SomeValue
!ImportValue SomeValue
AWS環境の構築ではCloudFormationを使わずTerraformを使っている方も多いと思いますが、YAML対応もしたということでCloudFormationを素直に使うのもオススメです。
Ansible
インスタンスを立てた後にAnsibleスクリプトを手動で流すのは面倒だったので、UserDataで最初にAnsibleの実行を入れるようにしています。Cloud DI Patternに近いものです。
#! /bin/bash -v
yum -y install ansible --enablerepo=epel
aws s3 cp --recursive s3://asaas-stg-private/ansible ansible\n
cd ansible
ansible-playbook webserver.yml
reboot
自動起動/シャットダウン
コスト削減のため、必要ないリソースは自動シャットダウンするようにしました。CloudFormationのStackを作ったり消したりで実現しています。
スケジュールはGoogle Calendarで管理しています。Rubyで書くと下記のようなコードになります。
require 'google/apis/calendar_v3'
client = Google::Apis::CalendarV3::CalendarService.new
client.authorization = Signet::OAuth2::Client.new(
token_credential_uri: 'https://accounts.google.com/o/oauth2/token',
audience: 'https://accounts.google.com/o/oauth2/token',
scope: ['https://www.googleapis.com/auth/calendar'],
client_id: 'client_id',
client_secret: 'client_secret',
refresh_token: 'refresh_token',
)
client.authorization.refresh!
res = client.list_events(
'calendar_id',
single_events: true,
time_min: time.iso8601,
time_max: (time+1).iso8601,
)
if res.items.count > 0
# start environment
else
# stop environment
end
環境管理ツール(Rails製)
ステージング環境が起動したりシャットダウンしたりするので、環境の状態を見たり操作したりできるツールを作りました。
Railsで作っていますが基本的に操作ログ以外はDBを使わず、状態の表示などは常にAWS SDKを使って取得したものを表示しています。自分がさくっと(1日程度で)作るクオリティだと、DB上は起動してるはずなのに起動していない、となって混乱を招くことは容易に想定されたのでこの方針にしました。今のところはうまくいっているので選択は間違っていなかったかなと思っています。
環境操作を間違えないようにするChrome拡張
社内アプリとしてサーバ類を操作するものがあるのですが、ステージング環境と間違えて本番環境を操作してしまうことがありました。
それを避けるためにChrome拡張を作りました。URLを見て特定のURLだった場合にdivを差し込むだけの仕組みです。ついでにAWSコンソールの色も変えられるようにしています。
作成した拡張機能はChromeストアの限定公開の仕組みで社内向けに公開しています。
マルチアカウントでAWSコンソールにログインできるElectron製アプリ
これは社内でというか完全にフリーランスとしての個人用なのですが、他の複数の案件を含めるとAWSアカウントを7つほど管理しなくてはいけなくなったのでブラウザのウィンドウ切り替えが面倒になってきてしまったのでこんなものも作りました。
詳細は別途ブログに書いたので興味ある方はこちらを参照してください。コードも置いてあります。S3のファイルのダウンロードが動作しないところは早々に直したいところです。
感想(と宣伝)
さくっと行けるかなと思ったのですが、インフラ周りはどうしても時間かかりますね。動くようになったものの、まだまだ改善中です。もう少しモダンな形にできたらという部分はあったのですが、まずは今後の継続的な改善ができる状態に持っていくことを目標として、それは達成できたかなと思っています。
A-SaaSは会計事務所向けのクラウド会計システムで、10万以上の顧問先の会計を担っているミッションクリティカルなシステムですが、そんなインフラを触れる機会はなかなかないので非常にエキサイティングです。アカウンティング・サース・ジャパンでは更にシステムを良くしていく仲間を広く募集中です。
もっとイケてる環境構築ができるぜという方、ぜひご応募ください。