これだけ押さえておけば大丈夫!Webサービス向けVPCネットワークの設計指針

はじめに

こんにちは!エウレカの恩田です。
今回は、AWSを用いたWEBサービス向けネットワーク設計についてお話します。

VPCとは?なぜVPCを使うのか

前提として、AWSではプライベートネットワークを作成せず、グローバルな領域に
サーバを作成してリクエストを受ける事も可能です。グローバルIPアドレスが付加されますので、単純にWebサーバとして稼働させるのであれば、このまま使うことができます。このようなサーバを複数個作成し、ロードバランサ、Appサーバ、DBサーバと用途別に作成しシステムを構築する事も可能です。

ただし、これでは各サーバが不特定多数からのアクセスを許すことになります。
各サーバ毎にIptables等でセキュリティ設定を行いアクセスを制限することはできますが、非常に面倒です。また、万が一セキュリティホールがあった場合、特にPairsのようなお客様の個人情報を扱うサービスでは致命的なリスクになります。

このセキュリティ上の面倒を簡単に解決できるのが、Amazon VPC(Virtual Private Cloud)を使う利点です。VPCを用いる事でネットワークをプライベートに作成したり、
IPアドレス等でのアクセス制限やプロトコルの制御等、セキュリティ上の要件をまとめて設定する事が可能です。

VPCを設置すること自体に料金はかかりません。
つまり「とりあえずサーバはVPC内に設置」としておくと、トラフィックの制御等を後からでも細かく設定できるメリットが得られるのです。

ネットワーク設計コストとWebサービスにおける設計の定石

とはいえ、VPCの利用自体はタダでも、設計にかかるコスト(人的、時間的)は無視できません。特にAWS初心者には独特の概念もあり難しい部分でもあります。
(自分も去年までは完全な素人でした、、)

ですが、Webサービス向けのネットワーク構成はある程度雛形が存在すると考えており、基本の形を抑えておけば、いくらでも応用できると自分は考えています。
そこで今回は、あるあるなWebサービス構成を例に、どのようにAWS上でネットワーク構成を組むべきかを記事にしてみました。

システム要件

以下のようなシステム要件でネットワーク構成を考えるものとします。

  • ユーザアクセスはロードバランサ(ELB)で受ける。DNSはroute53を利用
  • Appサーバが1 ~ 〇台、ELBの配下に存在
  • DBサーバをWebサーバと切り離し、別筐体として用意

他にもバッチサーバやキャッシュサーバ等々、規模によって登場人物はいるかと思います。が、ほぼ全てのWebサービスのミニマム構成は上記のような ELB、App、DBの3層構成になるかと思います。

PublicレイヤとPrivateレイヤの分離

サブネットレイヤでまずはネットワークを分離します。
アクセスの受け方によって2種類に大別します。

  • Public:インターネットから直接通信を受けるサーバを配置
  • Private:EIPを持たず外部から直接通信を受けないサーバを配置

今回の場合だと、Publicに所属すべきはユーザから直接リクエストを受けるELB、Privateに属するべきなのはAppサーバやDBサーバという事になります。

ルートテーブルでPublicレイヤに属するサブネットのデフォルトゲートウェイを
Internet Gatewayに向けてやる事で、外部との通信が可能になります。
(Privateレイヤのサブネットのルートテーブルについては後述します)

ユーザアクセスの制御

次に、Public/Privateでそれぞれ許可すべきアクセス元を考えてみます。
ユーザはroute53で名前解決されたELBにアクセスを向けるので、以下のようなルートでAppサーバへリクエストを送信する事になります。

access_controll

各サブネットに紐づくNetwork ACLで、許可するアクセス元を制限します。

Public Subnet向けNetwork ACL

  • Internetから直接リクエストを受けるので、アクセス元は絞らない(全世界に開く)
  • 全プロトコル、全IPアドレスのリクエストを許可

Private Subnet向けNetwork ACL

  • ELB ~ App、App ~ DBといった、VPC内部間で完結する通信が基本
  • 許可するアクセス元IPアドレスのCIDRをVPCネットワーク内に限定する

Public / Privateそれぞれで許可するIPアドレスのCIDRを設定する事で、
悪意あるユーザからのリクエストを弾く事ができます。
(プロトコルの制御については別途Security Groupの項で説明します)

開発メンバによるsshログインとプロビジョニング

次に、開発メンバーによるネットワーク内サーバへのsshログインポリシーを考えます。
障害調査やdeploy、プロビジョニング等の目的で開発メンバーが本番環境サーバへssh
ログインしたいという要望は簡単に想像がつきます。

とはいえ、現在の構成ではAppサーバやDBサーバへは直接sshできません。Private Subnet向けのNetwork ACLで外部からの通信を許可していない為です。

Bastionサーバの設置

このような場合、Bastionサーバと呼ばれるsshの踏み台サーバをPublicネットワーク内に設置し、Private Subnetに属するサーバへの侵入経路を限定した上でssh可能な経路を作ってやるのが定石です。

内部ネットワークへの侵入経路をBastionサーバに集約することで、検知の仕組みの導入や
不意な情報流出(例えば、社内メンバの一人がPCなくした等)時の対応が容易になります。

BastionサーバはPublicなレイヤに属するので、一般ユーザからのsshも受け付けられる事になります。なので、パスフレーズではなく公開鍵認証でユーザ認証を行います。

ssh_from_developer

プライベートネットワーク内からのプロビジョニング

deployやプロビジョニングを行う場合、AppサーバやDBサーバにsshできるサーバが必要
になります。以下のようにPrivateネットワーク内に設置してやり、Bastionサーバ経由でsshログイン、各種操作を行う構成がよいかと思います。

deployサーバにはAWSのcredentialやDBパスワード等の機密情報が設置されてる事が多いので、社内メンバでもアクセスを絞りたいケースが多いかと思います。Bastionサーバで経路を限定しているので、Bastionサーバの公開鍵管理だけでネットワーク内にsshできるユーザを管理できるのもBastionサーバを利用するメリットです。

deploy_server

Privateネットワークに属するサーバの外部通信

次に、Privateネットワークに属するサーバが外部へ通信する際の経路を考えます。
ユーザアクセスは全てELBを経由するのでレスポンスを返す分にはELBとの通信ができれば
問題ないです。が、以下のようにPrivateネットワークに属するサーバが直接外部と通信したいケースが往々にしてあります。

  • Fluentdを用いてBigqueryへログを送信したい
  • Mackerel-Agentからメトリクスデータを送信したい
  • yumレポジトリからnginxをインストールしたい
  • Deployサーバで、git管理されているリモートレポジトリをクローンしたい

NATサーバの設置

このような場合、NAT(Network Address Translation)サーバを設置し、外部への
通信路を確保します。

NATサーバは、Privateネットワーク環境下でプライベートIPアドレスを持つホストから、グローバルIPアドレスを持つゲートウェイを通して、インターネットにアクセスする際に、プライベートIPアドレスをグローバルIPアドレスに変換するために利用されます。

Appサーバ等のPrivateサブネットに属するサーバは、EIPを持つNATサーバを経由して外部との通信を行います。以下の図で診てもらうとイメージし易いかと思います。

NAT

PrivateサブネットのRouteテーブルでのデフォルトゲートウェイにNATサーバを指定することで、NATサーバ経由で外部と通信が可能になります。

AWSでは昨年末にNAT Gatewayと呼ばれるフルマネージドNATサーバを提供するサービスが開始されたので、これを利用すると良いかと思います。

Security Groupによるプロトコル制御

次に、各サーバ毎のプロトコルの制御を実施します。AWSではSecurity Groupでこれを
実現します。ちなみに、Network ACLとSecurity Groupは、いずれもファイヤウォールとして機能するのですが、ACLの方はサブネット単位で適用するのに対して、Security Groupはインスタンス単位で適用します。

設定できる項目は似ているのですが、重要な違いは、Security Groupは「ステートフル」なのに対して、ACLの方は「ステートレス」です。要するに、Security Groupはインバウンドかアウトバンドの通信のどちらかを許可すると、それに対する応答の通信は自動的に許可されます。一方、ACLの方は、インバウンドとアウトバンドそれぞれのルールを作っておかないといけません。

ACLの方は、サブネット単位で影響を行使できるので、ブラックリストの登録などに利用するのが良いかもしれません。その上で、インスタンスごとにSecurity Groupで細かい設定を行うのが基本になります。エウレカではNetwork ACLで接続元IPアドレスをCIDRで制御(ブロック)、Security Groupで各サーバ毎のプロトコルを制御、という使い分けをしています。

サーバ毎にInboundで許可するプロトコル

これまで出てきたサーバ事に、Inboundで許可すべきプロトコルを整理してみます。

  • ELB:http/httpsが全世界に開いてればOK
  • Bastion:TCP(ssh:22 or 適当なポート)が全世界に開いてればOK
  • App:VPC内からのhttp/httpsを許可してあればOK
  • DB:VPC内からのTCP:3306(mysql想定)が許可してあればOK
  • Deploy:VPC内からのTCP(ssh:22 or 適当なポート)が許可してあればOK

以下が各サーバ毎のネットワーク / Security Group適用イメージです。

SecurityGroup

Multi-AZによる冗長構成と高可用性の実現

ここまでで、一通りの環境が出来ました。ですが可用性の観点でいうと、もう一仕事必要です。現在の構成では、一箇所のデータセンターが不意の障害に見舞われた場合にサービスも即停止してしまいます。

Availabirity zoneとサーバのMulti-AZ配置

AWSではAvailabirity zoneという概念があります。これは、1つのリージョン内で
物理的な設備が別々になっている領域を指します。同じリージョン内のデータセンターでも、電源、空調、ネットワーク機器という物理的な設備が全く別系統になっており、一方のAZに障害が起こっても、もう一方のAZでは問題が起こらないようになっているそうです。

各サブネットをMulti-AZ構成で作成し、各サブネットにAppサーバやDBサーバ、ELBを配置することで、どちらかのデータセンターで大規模障害等が発生した場合もサービスを止める事なく稼働させる事ができるようになります。以下が、Public/PrivateネットワークでそれぞれMulti-AZを適用した図になります。

Muti-AZ

※ 図のDBはマスタDB + マスタのホットスタンバイ機として描いています。あくまで高可用性についての話なので、マスタ・スレーブ構成での負荷分散についてはここでは言及しません。負荷分散(処理能力)と可用性(稼動率)は分けて考えることが大切です。

まとめ

ネットワークは一度稼働させると移行が大変なので、初期設計が非常に重要になります。
ですが、規模にかかわらずWebサービスには、ある程度ネットワーク設計の方針があり、定石を知っているだけで構築がラクになると思います。自分は元々サーバサイドエンジニアだったのでネットワーク設計のイロハも知らず、最初色々苦労した経緯があったので、この記事がどなたかの助けになれば幸いです。

今回の記事では概念的な説明が多く、実際作り方よくわかんねーよ!的なツッコミもありそうなので、次回はこれらAWS上のネットワーク構成をTerraformでコード管理している話辺りを書きたいと思います。ではまた!

  • このエントリーをはてなブックマークに追加

エウレカでは、一緒に働いていただける方を絶賛募集中です。募集中の職種はこちらからご確認ください!皆様のエントリーをお待ちしております!

Recommend

オウンドメディアの古い記事を整理して、トラフィックを約2倍にした話

Golang におけるサブテストの並行処理実装について