オンプレミスとGKEを併用したインフラについて

こんにちは、IT基盤部の宮崎です。
DeNAが提供するヘルスケア系サービスのインフラを担当しています。
今回は、クラウド移行とアーキテクチャ刷新に取り組んでいるプロジェクトがオンプレミスとGKEを併用する構成になりましたので、クラウド移行の過渡期にあたるシステムの紹介を通し構築過程で得た気づきをお伝えさせていただきます。詳細はドキュメントに頼る事が多く説明不足な所があると思いますが、何か得られるものを見つけていただければ幸いです。

概要

はじめに、アーキテクチャ刷新の目的とクラウドプラットフォーム選定の経緯を簡単に紹介します。
その後、システム概要図から、クラウド環境に焦点をあて分割していきます。
アプリケーションはGKE上で動作し、SharedVPCで払い出すネットワークを利用してオンプレと通信します。

アーキテクチャ刷新の目的

オンプレミスのシステムに以下の課題を持っていました。

  • 事業展開スピードに合わせて、プロダクト改善を進められるようにしたい
  • テストの所要時間が長い
  • デプロイにかかる時間が長い
  • コンポネントを容易に増やしたい
  • パフォーマンス面の懸念を動的に解決したい

それを解決するためにコンテナ技術を活用,マイクロサービスアーキテクチャを採用することに決定。

クラウドプラットフォーム選定の経緯

以下の要件や構想を元に、AWSGCPを候補に検討をはじめました。

  • アプリケーションレイヤーにKubernetesを利用する。
    • 懸念無し (ECS, EKS, GKE)
  • 外部連携のデータ受け渡しは専用線を利用する。

  • マネージドサービスを積極的に利用する。

    • CloudMemoryStoreがベータいつGAになるか不明
    • CloudSQLのメンテナンス時間 (CloudSQL Proxyの管理どうする)
    • 懸念なし (LB, RDB, Redis, Storage 等の利用予定のマネージドサービス)
  • BigQueryを分析で利用する。

どちらも機能は備えているが、クラウド上に我々の運用フロー(作業証跡・認証)を別途準備するのは変わらない
BigQuery利用する事が決まっているから、アカウント管理を考えるとGCPに統一した方が楽なのではないか?この事からGCPをメインで検証を進め、最終的にこのプロジェクトはGCPを利用する事に決めました。 2018年秋頃に検討したものなので、2019年9月現在は状況が変わっていると思います。

システム概要図

システムの構成要因は、以下の通りです。

  • システム環境
    • GKE環境
    • オンプレミス環境
  • 外部連携システム
    • ファイル授受
  • 外部サービス
    • 外部API連携
  • システム利用者
    • サービスユーザ
    • 社内メンバー(企画、CSメンバーなど)

これらの関係を図示すると以下のようになります。

onpre_gke_summary.png

オンプレミス環境には既存システムが存在しています。GKE環境からオンプレのSFTPサーバ・メールサーバを利用しています。オンプレ依存を少なくするため外部メールサービス利用を検討しましたが、完全移行まで費用を抑えたい事と海外事業者への第3者情報提供をしないなどの制約を満たすため、完全移行まではオンプレのメールサーバを利用する事にしました。

アプリケーションは全てGKE上で動作し、積極的にマネージドサービスを利用しています。
GKE環境を、クラウドサービスのコンポネントで置き換えると以下のような構成です。

k2_design.jpg

次に、オンプレとどのように接続しているかを説明するため、ネットワークとGCPプロジェクトについて紹介します。

ネットワークとGCPプロジェクト

GCPのネットワークリソースは、リージョン・プロジェクトを跨いで存在できるので、HostProjectで一括管理しServiceProjectにリソースを払い出す事にします。こうするとオンプレとGCP間のVPN接続を3環境(開発, ステージング, 本番)準備する必要があるところが1環境に集約出来てVPN接続を減らすことが出来ます。


VPN接続 一系統(2本)の費用 約[110 USD/月]

ネットワーク管理とログ管理の2つの視点から次の通りGCPプロジェクトを分割しています。

  • HostProject
    • ネットワークの一括管理、SharedVPCでServiceProjectへ払い出す、VPN接続・Firewallルール管理
  • ServiceProject
    • HostProjectから払い出されたネットワークを利用してGKEクラスタ上でアプリケーションを動かす
  • AuditProject
    • 監査ログを保存するプロジェクト

これらの関係を図示すると以下のようになります。ServiceProject, AuditProjectのみ環境毎に存在します。

NW_vpn_SharedVPC.jpg

SharedVPCとアドレス設計

SharedVPCでServiceProjectにネットワークを払い出すのですが、クラスタサイズを決めるためアドレス設計が必要になります。

Defaults and Limits for Range Sizes の通り、標準設定では大量のIP(Node:/20 + Pod:/14 + Service:/20)を必要とします 同様に潤沢に割り当てたいところですが、オンプレと通信するため他の環境と重複しないようにアドレス管理が必要になります。 オンプレとクラウドのネットワークの話の通りサービス数が多く、サービス規模に合わせたアドレス設計が必要です。

 "Considerations for Cluster Sizing"を参考にNodesを決めるとPodが決まります。
 ServiceのIPは削減できると思いますが、GKEクラスタ操作用のoperation networkも含め(/15)に収まるようにしています。

 ## 合計 (/15)
 -------------
 # GKEクラスタ
 Nodes   : /24
 Pod     : /16
 Service : /20

 # Operation
 bastion : /24

 クラスタを追加する場合には、Serviceネットワークのみ別途割り当てる。
 性能不足にはNodesのScaleUPで当面対応できると考えてました。

これらの関係を図示すると以下のようになります。

SharedVPC_GKE.jpg

GKEクラスタ

ネットワーク割り当てが完了しましたので、クラスタを構築したいと思います。
セキュリティを考慮し限定公開クラスタとし、マスターの制御元をoperation networkに限定するためマスター承認済みネットワークとします。

キーワードと、ドキュメントは以下を参考にしています。

  • 共有VPC内に限定公開クラスタを作成する
  • 限定公開クラスタ
    • VPCネイティブクラスタ
    • VPCネットワークピアリング
  • マスター承認済みネットワーク

  • VPC Network Peering

  • authorized networks

以上をまとめると、クラスタ作成するコマンドは次のようになります。

 cluster_name=FOO-BAR-platform-green
 network_project_id=dena-FOO-BAR-platform-nw-gcp  # HostProject  
 vpc_network=vpc-FOO-BAR-platform-1
 subnetwork=subnet-FOO-BAR-platform-1-ane1-node-172-17-100-0-24
 pod_range=subnet-FOO-BAR-platform-1-ane1-pod-172-16-0-0-16
 service_range=subnet-FOO-BAR-platform-1-ane1-service-172-17-0-0-20
 master_range=172.18.0.0/28
 authorized_network=172.17.200.0/24 # operation network
 num_node=6
 node_type=n1-standard-4

 gcloud beta container clusters create $cluster_name \
 --enable-autoupgrade \
 --enable-ip-alias \
 --enable-private-nodes \
 --enable-private-endpoint \
 --enable-master-authorized-networks \
 --no-enable-basic-auth \
 --no-issue-client-certificate \
 --master-authorized-networks $authorized_network \
 --network projects/$network_project_id/global/networks/$vpc_network \
 --subnetwork projects/$network_project_id/regions/asia-northeast1/subnetworks/$subnetwork \
 --cluster-secondary-range-name $pod_range \
 --services-secondary-range-name $service_range \
 --master-ipv4-cidr $master_range \
 --cluster-version=latest \
 --enable-stackdriver-kubernetes \
 --machine-type=$node_type \
 --num-nodes=$num_node \
 --tags=$cluster_name

最後にアプリケーションをデプロイし、セキュリティ構成を付加したサービスレベルの俯瞰図は次の通りになります。

service_security.jpg

最後に

今ではサービスを提供出来る状態になりましたが、ここに到るまでSharedVPC環境でクラスタが作成出来ないSharedVPC配下でGKE Ingressリソースが作成出来ないなど、構築過程でさまざまな問題がありました、エラーログを元に調査しても手がかりが無い場合もありサポートケースGCP Office Hourで相談しながら都度解決してきました。その情報は英語・日本語共にドキュメントを修正していただきましたが、日本語への反映には時間がかかる事に気づきました。

ドキュメントのリンク先を英語で書きましたが、これが今回お伝えしたい気づきです。

❗英語のドキュメントも読もう。❗

常識的な事かもしれませんが、日本語と英語のドキュメントの乖離はあり、一年以上に及ぶのもあります。

この文章を通して、オンプレとGKEを併用したインフラを紹介させていただきました。

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

DeNAにおけるLinux環境アラート対処ケーススタディ

はじめに

こんにちは。山本です。 現在、IT基盤部第二グループのマネージャをやらせていただいております。

私のグループではいくつかの領域を扱っていますが、その中で大きな比率を占めるのがオンプレミスのLinuxサーバの管理となります。 業界のトレンドとしてはパブリッククラウド側の基盤が騒がれていますが、まだまだオンプレLinuxも現役であるのは事実。それらのサーバに関して、いかにしてDeNAで管理・運用を実施しているのかという一部を 具体的に かつ 実際に起きている日常の実例を元に お見せしたいと思います。

そもそも管理とはなんなのか?

そもそも、我々が実施している仕事というのがなんなのか、ということですが、大きく分けて以下のものに分類されると考えています。

  • 構築・構成管理 新規のサーバ構築
    • あるべき構成とのずれの検出・修正
  • サービス(事業)側への対応
    • 新規のサーバ準備
    • その他諸々小さな依頼事項への対応
  • 負荷・アラート管理
    • サーバの状態レポートの作成
    • 異常が発生した場合のその原因検知

この中で「構築・構成管理」は各所でツールなどを利用して実施されていると考えますし、「サービス(事業)側への対応」は依頼内容によって異なることもあると思います。 今回は、「負荷・アラート管理」について、特定の例を挙げた上で我々DeNAインフラエンジニアがどのように判断してどのように対応しているか、といったものを示したいと思います。一部改変していますが、おおむね事実に沿っている例です。

ケーススタディ【メモリに異常あり】

以下のようなメールを受け取りました。これは我々の監視兼ツールの一種であり、空きメモリが一定値を下回った時に自動的に特定の種類のプロセスのうち、メモリを最も大量に確保しているものをkillしてしまうというものです。

 [2019/06/15 00:03:13]
 KILLED PROCESSES

 ============================================================================
 = 2019-06-15T00:02:25 =
 == SYSTEM ==
    TOTAL     FREE  BUFFERS   CACHED    REALFREE REALFREE%
 23989.36   268.39   590.23   285.82     1144.44     4.77%

 == KILLED PROCESSES (fat) ==
 STATUS  USER      PPID   PID CHILD     RSS    PRIV    SHAR SHAR% START               CMD
 KILLED  xxxx     42482 45594     0  2229.8   255.9  1973.9 88.5% 2019-06-14T23:58:10 index.fcgi

上の場合は、メモリの空きが4.77%となっていたので、PID 45594のindex.fcgiというプロセスをkillしたということを示しています。

初動対応

インフラエンジニアは科学者である前に、サービス運用に責任を持つ人物です。

したがって、「この原因はXXXだね」という評論の前に、降りかかる火の粉を払う必要があるのです。どんな時でも次の順序で進んでいきます。

  • 状況の確認(特にサービス影響の確認)
  • 影響が出ているならば、周囲にシェアした上で緊急処置
  • 影響が出ていない(or おさまっている)ならば、調査フェーズに

メモリの場合は緊急処置として以下のようなものが考えられるでしょう。

  • 案1:workerがあまっているならばworkerを減らす
  • 案2:緊急サーバ増設
  • 案3:アプリケーション側での緊急処置

今回のケースでは何度かアラートが飛んできましたが、簡単にできる対応として、まずはworkerを5ほど減らしてもらいました。

一旦おさまった後の調査と恒久対策

一旦おさまったのちには我々のグループでは次の調査を実施します。

  • CloudForecastによる短期的・長期的なメモリ推移確認
  • ログからリクエスト数推移確認
  • サーバ毎に1秒単位で取得しているログの確認(メモリ・CPUなど)

ただ漫然と眺めていてもあまり意味がありませんので、どのような観点で眺めているのかということと判断の基本的な流れを記述します。

  • メモリ問題は、リクエスト数増加に伴う自然なものか否か?
  • 特定のサーバで起きているのか、満遍なく起きているのか?
  • 特定のタイミングで急激にメモリが上昇しているのか、満遍なく上昇しているのか?

これは、我々のグループにおいては「テストに出ます」というレベルで必要な観点です。

memory_flowchart.png

考えてみれば当然の流れだとは思います。アプリケーションコードやプロファイルを即座に眺めて詳細に調査する必要はありません。

今回のケースでは、残念ながら、特定時刻以降でどのサーバでも満遍なくメモリが増えているという状況でしたが、どちらかというとタイミングによってたまにガクッとメモリを確保していることがあるようでした。 これは、何らかのコードを通った時に問題が発生する可能性を示唆します

cf_traffic.png

trafficはもちろん上下はあるものの、特段メモリの事象が起きた際に増えているわけではありません。

                          procs -----------memory---------- ---swap-- -----io---- --system--  -----cpu-----
                          r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 20190614 00:01:40        7  0      0 1595940 678600 428780    0    0    56   228 21715 9998 13  2 84  0  0
 20190614 00:01:41        5  0      0 1418176 678600 428816    0    0     4   176 17218 7143 11  2 87  0  0
 20190614 00:01:42        5  0      0 1426624 678648 428880    0    0    68   184 18987 7940 13  2 85  0  0
 20190614 00:01:43       10  0      0 1207144 678648 429004    0    0    96   860 19715 8138 11  3 86  0  0
 20190614 00:01:44        4  0      0 1353092 678648 428940    0    0   100   164 19512 8349 14  2 84  0  0
 20190614 00:01:45        9  0      0 999816 678648 428980    0    0     4   212 22501 9151 17  3 80  0  0
 20190614 00:01:46       10  0      0 633396 678648 429036    0    0     4   196 24249 9229 18  3 79  0  0
 20190614 00:01:47        7  0      0 668244 678648 429056    0    0     0   148 24446 7356 26  3 71  0  0
 20190614 00:01:48       10  0      0 375356 678732 428764    0    0   144  1360 22122 11070 15  5 80  0  0
 20190614 00:01:49        8  0      0 453368 678736 428900    0    0    44   164 22485 8921 23  1 76  0  0
 20190614 00:01:50        8  0      0 475648 629532 417120    0    0     8   200 33972 9238 21  2 77  0  0
 20190614 00:01:51       12  0      0 414768 629532 417184    0    0     0   144 27192 11090 15  6 79  0  0
 20190614 00:01:52        4  0      0 545636 629616 417188    0    0    92   196 22731 9139 15  4 81  0  0
 20190614 00:01:53        8  0      0 171572 629620 417216    0    0     4   720 18312 7093 11  2 86  0  0
 20190614 00:01:54        7  0      0 411384 508808 384700    0    0   268   220 25455 7998 15  5 80  0  0
 20190614 00:01:55        7  0      0 358036 473876 292412    0    0   596   144 28311 6510 15  6 80  0  0
 20190614 00:01:56        6  0      0 489696 473876 292988    0    0   448   164 22794 7818 10  5 85  0  0
 20190614 00:01:57        4  0      0 377520 473876 293024    0    0    12   140 22169 8901 15  2 82  0  0
 20190614 00:01:58        5  0      0 365716 473916 292848    0    0    92  1352 19942 12584 12  1 87  0  0
 20190614 00:01:59       11  0      0 144052 402256 200484    0    0    60   192 20951 6587 12  4 84  0  0
 20190614 00:02:00        4  0      0 810580 306132 173364    0    0   256   168 22044 5282 15  3 82  0  0
 20190614 00:02:01        9  0      0 685372 306148 173112    0    0   108   228 23284 9098 16  4 80  0  0
 20190614 00:02:02        8  0      0 773072 306492 173572    0    0  1360   160 24735 7460 13  7 79  1  0
 20190614 00:02:03        7  0      0 689528 306500 175876    0    0  1752   872 24791 8648 11  4 83  2  0

メモリは、一気に確保したり解放したりといった瞬間があるようです。例えば00:01:42から一気に空きメモリ(free)が減少しています。

アプリケーションコード

ここまでくると、仕方ないので真面目にアプリケーション側のコードの解析を実施します。 これも、漫然と眺めていても仕方ありませんので、DeNAで利用されているperl heap statを利用してみました。

  • perl heap statでの確認
  • (場合によっては)nytprofの確認

perl heap stat(phs)ではコードとその行番号を含めて示してくれます。 DeNAインフラの今の話 第2回「性能劣化、障害発生時のプロファイリングツールの紹介」 にはphsの実行例が出ていますが、問題箇所を的確に指し示してくれることがあります。

今回の場合には、特定のアプリケーションの特定のサブルーチンが示されていました。

 sub addData {
    my ($id) = @_;

 (省略)   
    my $dbh = DA::getHandle($handle);
    my $ret;
    if (exists($CACHE->{'master'})) {
            $ret = $CACHE->{'master'};
        } else {
        eval {
            $ret = SQLを利用し、DBからデータを取得してくるコード
            $CACHE->{'master'} = $ret;
        };
    }
 (省略)
 }

このPerlコードは、 $CACHE->{master}(グローバルなスコープを持つ変数であり、消えない) が存在すればそれを利用し、存在しなければ、実際にDBからデータを取得してきた上で、 $CACHE->{master}にセットするというものです。 特段問題はないように思えますが、ここのmasterにどの程度のレコードが含まれているかということが重要となります。

SQLで取得してきているテーブルを実際に検索し、そのデータ数を確認すると、これがそれなりに多いということがわかりました。その大量のデータを$CACHE->{master}に保持することによって問題が起きうる、ということが仮説として浮かびます。

といって、どうすれば良いのかということになります。$CACHE->{master}にセットせず、毎回SQLを発行してDBから取得するという方法があります。間違いではありませんが、クエリの増加、速度の下降といった問題があります。 我々がこの例で利用している仕組みでは、Perlはhttpdと別プロセスで動作しており、親となるPerlプロセスが各モジュールを読み込んで初期化しておいたのちに、そのプロセスがforkして、多数のworkerプロセスとなります。 したがって、親プロセスとなるべきプロセスで、 $CACHE->{'master'}をセットしておけば、そのメモリはforkされた子プロセスでもそのまま利用可能であり、なおかつCopy-On-Writeの仕組みによって、実際のメモリコピーはなされず、親プロセスで利用していたメモリを利用してくれます。

ですので、コードを次のように書き換えてもらうこととなりました。

 # 高負荷のため、preload化
 sub initializeMaster {
    my $dbh = DA::getHandle($handle);
    my $ret = SQLを利用し、DBからデータを取得してくるコード
    if($ret) {
        $CACHE->{'master'} = $ret;
    }
 (省略)
 }

 initializeMaster();

 # こちらは変更しない
 sub addData {
    my ($id) = @_;

 (省略)   
    my $dbh = DA::getHandle($handle);
    my $ret;
    if (exists($CACHE->{'master'})) {
            $ret = $CACHE->{'master'};
        } else {
        eval {
            $ret = SQLを利用し、DBからデータを取得してくるコード
            $CACHE->{'master'} = $ret;
        };
    }
 (省略)
 }

つまり、今までの処理は変更せず、単純に $CACHE->{master} をモジュールロード時に実行される initializeMaster() でセットしたということになります。

preload.png

preloadに関連する基本的な知識ではありますが、こういったものを調査して、実際にアプリケーション開発側にお伝えして具体的な再発防止策として実施する、というところまで行けるのがベストです。(その後、再発していないことを確認することももちろん重要です。)

このような問題は、開発環境などでは確認しにくく、また、DBのレコード数が徐々に増加してくることによって顕在化してくるとか、該当のコードを通りやすいようなページ構成になるとかいった要因によって発生するものであり、開発中に気づくのは非常に難しいと言わざるを得ません。したがって我々インフラエンジニアが気づいて、本格的な問題が起きる前に修正してもらうのがベストだと考えています。

なお、実際の運用において、素直に全てのメンバーが一本道でゴールにたどり着くというわけではありませんし、「この問題は調査打ち切りとする」というような場面もあります。

他のケースの場合

今回のケースは実際に日常的に起きている例の一つであり、最終的にアプリケーション開発者に連絡して再発防止策実施にまで至った例ですが、この一つを取ってもインフラエンジニアとして ロジカルに判断 しつつ、 背景として技術的知識を利用 する必要があります。 単純に「よくわからないけれどサーバ増設だ!」「手当たり次第に調べてみるぜ!」といったものではなく、都度状況を判断しつつ必要なものを調査し、ゴールに向かっていくという流れとしたいところです。

また、例としてメモリアラートを出したのは、最近私の管轄内で頻発しているからですが、上に書いた進み方の全体方針の考えは他のアラートでも同様です。 「よくわからないけれどネットワークが切断される」「よくわからないけれど、レスポンスが遅い」といった問題に対して切り込むメソッドを磨くことで、インフラ技術者としての練度を上げるべく努力したいと思います。


今回の話の背景知識

webのバックエンドでworkerを走らせる場合のメモリとworker数の関係

上で、workerを減らすという話があります。 workerがあまっているならばこれはメモリ確保のための非常にお手軽な解決策となります。とはいえ、例えばworker数を2/3にすればメモリの使用量が2/3になるというような簡単なものではありません。

memory_workers.png

実際には上の図のように、worker数を減らしても、一般に1workerあたりのメモリ使用量は増加するので、そこまでの効果はありません。 これは、いままで6個のworkerで受けていたリクエストが4個のworkerになったので、1 workerあたりのリクエスト処理量が1.5倍に増えるためです。

あとはアプリケーションプログラムの書き方によるのですが、例えば、リクエストを受けるたびに、プロセスメモリ上にキャッシュとして確保などしているとすれば、プロセスが受けるリクエスト数が増えれば、それだけプロセスのメモリ確保は多くなるのです。当たり前ですね。

といって、効果がないわけではありませんし、お手軽に実施可能ですので、まずはじめに検討してみても良いと思います。

workerがあまっているならば...?

お気軽に「workerがあまっているならば」などと書いてしまいましたが、あまっているかどうか、どうやって知ることができるのかという話があります。

DeNAのperl環境においてはperlstackという仕組みがあるので、実際に遊んでいるworker数を正確に算出していますが、どこでもその方法が使えるわけでもありません。 しかし、それがなくとも簡単に概算できます。

  • 単位時間あたりのリクエスト数
  • 平均レスポンスタイム

この二つがわかれば十分です。単位時間(例えば1分)にリクエストが1000きているとして、平均0.3秒でレスポンスを返しているとしましょう。 すると、1分あたりで、実際に処理が動いているのは「のべ」300秒ということになります。1分(60秒)の間に300秒分の処理をするためには1workerでは足りず、5workersは最低でも必要となります。実際には、リクエストのバラツキもありますから、workerの「待ち時間」というものが発生します。 その分を考えて、余裕を持ったworker数を準備する必要があります。

num_of_workers.png

しかし、例えば「1分に100リクエスト、平均レスポンスタイム0.5秒」という状況で20 workersが用意されているとすれば、それはあまっていると判断して差し支えないでしょう。 もちろん、ピーク時を考慮してリクエスト数は検討する必要がありますが、明らかに過剰と判断すれば、無駄なメモリの肥やしを作る意味はありません。

Perlとメモリ

Perlはメモリの管理が必ずしも先進的ではなく「本物の」Garbage Collector(GC)を持っておりません。 実際に我々の管理しているサーバにおいても、ほっておくとメモリがどんどん溜まってきてしまう例があります。本来的にはメモリに対する全ての参照を破壊すれば、その変数の参照カウントがゼロになるはずですが、おそらくそうなっていない何かが存在することでメモリがどんどん増加するのです。

通常のGCでは、根元から辿って行ってたどり着けるメモリにマークしていって、マークされなかった変数に対して回収をかけます。しかし、Perlの場合には参照の有無をみるだけなので、根元から辿り着けなくとも互いにリファレンスで参照しあっているような変数があれば、最後まで回収されません。一番シンプルな例としては、my $var = \$var; って書いておくと、myのスコープとか関係なくメモリ回収されないということです。

これをどうすべきか、という話では、「もっと近代的な言語に切り替える」「真面目にメモリリーク検出ツールを用いる」といったものもあると思います。しかし、我々の利用しているXSなどを含めたコード全体を見直す手間などを考慮すれば「定期的にプロセスを入れ替える」という現実的な選択肢に落ち着いてしまいました。

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

安心・安全・安価な日中間接続~Alibaba Cloud CEN~

こんにちは、IT基盤部第三グループのジュンヤと申します。DeNAに入社してちょうどこの9月で丸9年になります。 入社当初はネットワークエンジニアとして着任しましたが、それから、金融系・EC系システムのインフラに携わり、現在は社内システムのインフラエンジニアのマネージャをしています。ちなみに、FirstNameを名乗っている理由は、同じ部署に同じ名字のメンバーがいるためであり、様々な事故防止が理由であって、それ以上でも以下でもありません。

社内システムのインフラエンジニアとは?

当たり前ですが、インフラエンジニアと一口に言っても、担当しているシステムによって必要なスキルセットは異なります。その中でも、社内システムのインフラエンジニアというポジションは、雑多な幅広いテクノロジーに触れることができます。 管理しているサーバOSという面ではLinuxはもちろん、WindowsServerOS(いまだに2008とか・・・もうすぐ撤廃予定)もあり、仮想化基盤ではKVM、VMwareなどがありますし、ミドルウェアに目を向ければ、ActiveDirectory、MySQLやOracleといったRDBMS、Github:Eや様々なSaaSなどの運用も行っています。 このように、社内システムは多種多様なテクノロジーが混在し、日々、バックオフィスを支える縁の下の力持ちとして運用業務を行っていますが、もちろんそれだけではなく、スタッフから寄せられる要望や問題解決にあたることもあります。今回はその中の一つの例を紹介したいと思います。

「VPN接続ができないんだけど・・・」 〜中国からの便り〜

第一報

もう6月なのに肌寒い日もあり、今年は夏が来ないのでは?などと思っていたある日、あるスタッフからslackで問い合わせがありました。
見ると、週末から突如VPN接続ができなくなった、とのこと。VPNサービスは自宅などからリモートワークをするために必要ですし、それこそインフラエンジニアがメンテナンス目的で多数のサーバへのアクセスをするためだったり、SourceIPでアクセス制限をしているクラウド上のシステムを利用するためだったりと、言うまでもなく社内システムの中でも超重要なサービスの一つです。
嫌な汗が流れそうになるのを感じながらも、どのようなエラーがでているか聞くと、slackに以下のメッセージが貼られます。

 L2TP-VPNのサーバが応答しませんでした。接続し直してください。
 それでも問題が解決しない場合は、設定を確認し、管理者に問い合わせてください。

できれば遭遇したくないエラーメッセージの一つです。
「応答しませんでした」と症状を訴えているところを見ると、クライアントは接続しにいったけど、サーバが応答しない、もしくはサーバまで到達できなかったという問題になります。
ここで、サーバに問題があるとなると、オオゴトになります。VPNサービスは前述したとおり、平常時から利用されているサービスですので、障害が発生するとそれなりの騒ぎになりますが、ざわついているslackチャンネルも見当たらないし、問い合わせも特になし。ましてや監視アラートも発報されていません。念の為にVPNサーバ側での認証ログをみても形跡がない。となると、クライアント側の環境か途中のネットワーク経路に疑いが持たれます。

原因特定。コレはアレでどうしようもないのでは・・・

ここで、問い合わせしてきたスタッフが中国に出張中ということにあらためて気づきます。そして、現地にいる別のスタッフにも同様にVPN接続ができるか念の為に確認を依頼してみると、結果は案の定、接続不可で同じエラーメッセージが表示されるとのこと。ここまできて、コレはアレで打つ手がないのでは・・と原因に目星をつけることになります。

「中国特有の現象」に遭遇

このblogを読まれている方には説明不要かもしれません。ピンとこない方は事象を検索してみて下さい。
今回の不具合は、十中八九、とある事情で接続できない事象(ここでは便宜上、「中国特有の現象」と呼ぶことにします)が原因だと判断した私は、突如戦意を失い、その旨を伝えます。

ジュンヤ「もうダメかもしれない・・・」
スタッフ「業務に支障があります」
ジュンヤ「わかりました・・なんとかしますので時間を下さい」

負けられない戦いがここにある。
サービスを直接支えるエンジニアではないけれども、サービスを作っているエンジニアをさらにその下で支えるエンジニアである、との誇りとともに、「中国特有の現象」に立ち向かうことを決意します。

Alibaba Cloud CENとの出会い

DeNAではオンプレからクラウドへの移行(Cloud Journey)が現在進行中です。現時点では主なクラウド基盤はAWSとGCPで、Alibabaは名前は聞いたことがあるけど(なんか強そう・・・)、というレベルでした。
そんな状況の中、今回のミッションである「中国から日本のシステムにVPNを張る」ために、Alibaba Cloudを利用するという構成が選択肢としてあがりました。
構成案は以下の通りです。

cen1.png

CENを利用したVPN構成

ポイントは「CEN(Cloud Enterprise Network)」というサービスです。これはリージョンの異なるVPC間を簡単にプライベートネットワークでつなぐことができる、というものです。 https://jp.alibabacloud.com/product/cen

「中国特有の現象」の影響を受けないためには、日中間で専用線を敷設することが必須というのがこれまでの常識でした。しかし、そのためには物理的な拠点が必要ですし、コストもリードタイムもバカになりません。 専用線の代わりにCENを利用し、VPNServerをクラウド上に配置することで、安価で柔軟性があるVPN接続ができそう、と期待が持てます。

実際に使ってみる

WEBにはCEN活用例がすでに紹介されていて、Step by Stepでの解説もされています。ありがたいです。
とはいっても、Alibabaを使うのは初めてのため、どれくらい手間がかかるのかわかりません。早速アカウントを作成し、実際に使いながら設定をしてみます。
無料枠(30,000円分)もついていますので、検証目的で気軽に始めることができるのも良い点です。 ログインしてみた第一印象は、「とっつきやすそう」。ユーザーフレンドリーな感じが好印象です。

ali1.png

シンプルなコンソール画面。アバターも設定できたりします
ali2.png
ローカライズも問題ないレベル

具体的な設定手順はここでは割愛します。前述したとおり、既に参考になるサイトがありますし、直感的に操作できます。
CENの注意点としては、実際に設定した後、リージョン間をPINGで疎通確認をするレベルであれば、CENコスト0で可能ですが、実際にSSHログインしたり、curl等でアクセス確認することはできません。
これは、デフォルトでは帯域が1kbpsしかないことが理由で、実用的に使うには帯域パッケージを購入して適用する必要があります。(2Mbpsで約30,000円/月)
また、Alibabaコンソールにログインした画面からは何故かCENコンソールへの導線がありません。CEN関連の作業は別途画面を開く必要があります。(https://cen.console.aliyun.com/

経路を無事確保。セキュアなVPN通信とは?

触り始めて数十分で日中間でのAlibaba VPCで疎通確認をすることができました。想像以上に簡単です。
しかし、ミッション完遂までは、やるべきことはいくつかあります。ざっと以下のようなタスクがあります。

  1. VPNを利用するアカウント数と通信要件のヒアリング
  2. IPアドレス設計(各VPC、クライアントVPN)
  3. JP-RegionとDeNAデータセンタの接続
  4. CN-RegionでVPNServerを構築
  5. 接続テスト
  6. (様子を見て)CEN帯域幅の決定、帯域パッケージの適用

タスクリストだけ見るとすぐにコンプリートできそうですが、セキュリティには十分注意を払う必要があります。 VPNサービスという社内システムを安全に使うためのシステムにセキュリティホールがあっては意味がありません。 特に考慮したポイントは以下になります。

  • VPN接続時のMFA(Multi-Factor Authentication)化
  • VPNサービスのアカウントの自動棚卸し
  • エンドツーエンドでのセキュアな通信をするための仕組みづくり
  • 必要なIP/Portのみ開放する(SecurityGroup)

VPNを利用するアカウント数と通信要件のヒアリング

まずは中国からのVPN利用人数とどのような通信が必要なのかをヒアリングします。
今回は結果として、プロトコルはHTTPS/HTTPのみ、アクセス先は社内システムとそれ以外という分類になりました。 通信要件としてはシンプルなものと言えます。

IPアドレス設計、JP-RegionとDeNAデータセンタの接続

タスクの2と3については、私のグループの管理下ではないため、部内にある専門部隊のNetworkグループに相談をします。優秀なネットワークエンジニアが多数在籍しているため安心して相談することができます。実際に、この作業も非常に短期間で完了しました。Alibaba側で必要な作業としては、VPN-Gatewayというサービスを利用することになります。実際の構築方法については、これも参考になるサイトがありますのでそちらに譲ります。

CN-RegionでVPNServerを立てる

この時点で、Alibaba CN-RegionとDeNAデータセンタの疎通確認が取れる状態になりました。ただし、セキュリティの観点からDCのInbound向きACLはほぼ閉じた状態です。
まずは入り口をしっかり守るという点で、VPN接続にはID/Passwordの他にMFA(Multi-Factor Authentication)を必須としました。ID/Passwordだけでは万が一、漏洩した場合に第三者がそのクレデンシャルを使った「なりすまし」リスクが想定されます。 MFAに対応したVPNソリューションはいくつかあると思いますが、ここではOpenVPN AS(Access Server)を選定しました。デフォルトでGoogleAuthenticatorに対応しています。
mfa.png

ログインにはID/PASSWORDと認証コードが必要

また、接続アカウント(ID)についてもVPNServerのローカルで情報を持ってしまうと棚卸しが大変です。退職等によって権限を剥奪する作業を別途行う必要があるからです。 この点、OpenVPN ASはLDAPに対応しています。DeNAはActiveDirectoryによってスタッフのアカウントの一元管理を行っており、LDAPも話せます。ADとLDAP連携することで、退職者についてはAD上で無効化されたと同時にOpenVPNにもログインできなくなり、別途棚卸しする必要がなくなりますので、この点も◎です。

ldap.png

OpenVPN設定画面より。専用のCNをADに用意すれば、VPN利用対象者を絞ることができる

パブリッククラウドだからこそセキュアな通信を

無事にクライアントPCからVPN接続ができたら一安心、としたいところですが、パブリッククラウドならではの注意するべきポイントがあります。オンプレミス環境とは違い、インフラをすべて自分たちで管理できているわけではないため、「盗聴リスク」も念頭におく必要があります。今回は以下の図のような構成にすることにしました。

vpn.png

エンドツーエンドでのセキュアな通信を考慮した構成

目指したことはクライアントPCからDeNA Data Centerまでの通信はすべてSSL化すること、です。

  • クライアントPC <-> OpenVPN Server間はSSL-VPN
  • OpenVPN ServerからLDAP Serverへの認証はLDAPS
  • クライアントPCからpacファイルへの通信はHTTPS
  • クライアントPCからプロキシサーバへの通信もHTTPS

このような構成にすることで、パブリッククラウド内では平文が流れることがほぼなくなり、盗聴リスクが大幅に軽減されます。
クライアントPCのブラウザにはプロキシを設定します。プロキシを間に挟むことで、アクセスログの取得もできますし、FWの開放Portを最小限に抑えることができます。また、直接、プロキシサーバを指定せずに、自動プロキシ構成(.pac)ファイルを設置したWEBサーバを準備し、そのアドレスを指すようにします。こうすることで、pacファイルによってトラフィックを複数のプロキシサーバに振り分けることができるようになります。DeNAでは、社外サイトへの通信はProxy-A、社内サイトへの通信はProxy-Bへ、といったようにセキュリティレベルにあわせたProxyを利用するようにしています。

仕上げ -各種ACLの見直しを忘れずに-

細かいところに気を配り、セキュリティ向上に努めたとしても、セキュリティグループやネットワークACLがガラ空きなことほど惨めなことはありません。全体構成を絵にして見直し、必要最低限のPortのみ開放するようにします。

最後に

現在、接続テストを実施していますが、大きな問題は発生していません。これから接続する人数を増やしてCENの帯域を増やす必要があるかどうかも判断していく必要がありますが、ワンクリックで即時に帯域を増やせるため、そこまで神経質に見極める必要はなさそうです。 パブリッククラウドという特性上、セキュリティ面には注意を払う必要がありますが、CENを使うことで、非常に簡単にコストを抑えたグローバルなプライベートネットワークを構築することができました。

「中国特有の現象」の前に、諦めたことがある、もしくは今まさに遭遇している、そんな方にCENを使ってみることをおすすめします。

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

オンプレとクラウドのネットワークの話

IT基盤部エンジニアブログリレー第4回目。
こんにちは、IT基盤部ネットワークG torigoe です。
オンプレとクラウドのネットワークをどう繋げるのか、クラウド内のネットワークをどうするのか考えた話を書きます。

多くの内部サービスを持っていて、オンプレからクラウドに移行を検討しているネットワークエンジニアの人に読んで頂ければと思います。

はじまり

2018年ある日、mtgに呼ばれて急に DeNAのインフラ全てをオンプレからパブリッククラウド(しかもマルチ)に変えようと思うがどうよ? と言われました。正直その日まであまりパブリッククラウドサービスを触った事がありませんでしたが、説明された内容には納得できたのでそんな事は噯にも出さず同意し、次の日からオンプレ-クラウド移行の為に必要なネットワークの検討を開始する事になりました。(元々Openstackは触ってました)

まずはAWSを色々と触ってみました。 ちらほらなんでこんな仕様なの?という疑問をいだきつつも、ドキュメント読んだり触ったりして大体使い方を把握しました。
AWSの次はGCPを触り、それぞれ次のような感想を持ちました。

  • AWSはユーザファーストの意識が高いように思える
  • GCPはアーキテクチャに拘っていて独自性が高い

要件を整理する

オンプレとクラウドを繋げるネットワークを設計するにあたって、まず要件を整理する事にしました。
ざっくり書き出すと、

  • 最終的にクラウドをマルチ(AWS/GCP)に使いたい
  • DR化も踏まえて検討したい
  • DeNAは非常にサービス数が多い=VPC数が多くなる
  • 各VPCとオンプレ間/VPC間はセキュリティを考慮しつつボトルネック無く通信したい
  • オンプレに多少サービスが残る可能性もケアしておく
  • オンプレネットワーク機器の保守期間の残りや延長可否について決める
  • ネットワーク運用にかかる工数を削減したい
  • ネットワークセキュリティも整理したい

といったところでした。
要件の中に一部弊社オンプレ環境の問題もありますので、今回のブログでは主にクラウドのネットワーク周りにフォーカスして書きます。

検討開始

現状のネットワークの課題整理、クラウド側のネットワーク仕様、最終形、という3つを意識しつつ検討を始めました。

オンプレ~クラウド間の接続

まずオンプレとパブリッククラウドの間を物理的にどうやって繋げるのか検討しました。 経験上インターネットVPNでの通信は遅延や品質を考えると、サービスで使えるものでは無いと判断していました。 ファイアウォールのスペック次第ですがVPN通信のスループットはそこまで大きな帯域を確保出来るものではありませんし、インターネット上をパケットが流れるので我々のコントロール外のネットワークで輻輳や障害が発生しパケロスが発生する事があります。

実際今までにAWS/GCPとのVPN接続を何本も張っていますが、運用中にそこそこの頻度でAWS/GCPとのVPNが落ちる事を経験していました。VPN接続も冗長化している為大きなロスにはならないですが、やはり多少のインパクトはあります。 移行期間中はマスターDBがどちらかにある状態なので、VPNを介したサービスはレイテンシやパケロスにより遅延が発生する事が想定されます。 という事で、本格的にサービス移行&サービス利用用途としての通信手段はVPNでは無く専用線を引くしか無いだろうと考え、専用線の物理接続構成の検討を進めました。

2018年検討時点でのクラウド側(AWS/GCP)の10G専用線接続ポイント(データセンター)は下記の通りでした。

  • AWS Equinix TY2(Tokyo)
  • AWS @Tokyo(Tokyo)
  • AWS Equinix OS1(Osaka)
  • GCP Equinix TY2(Tokyo)
  • GCP ComSpace1(Tokyo)
  • GCP Equinix OS1(Osaka)
  • GCP NTT Dozima(Osaka)

※201906現在GCPは@Tokyo(Tokyo)も接続可能です

弊社のオンプレ環境は上記DCにはありませんので、AWS/GCPと直接通信する為には弊社DCと上記DCのどこかの区間に専用線を敷設する必要があります。 検討を進めていく中で、オンプレに多少サービスが残る可能性がネックになってきました。 というのも、万一最終的にオンプレにある程度のサービス・ラックが残るとなると,最善の最終DC構成がコスト見合いで変わってくる事がわかってきたからです。 そこで一旦不透明な最終形は考えずに、とりあえず現状が使いやすいように専用線を引く方向で検討を進める事にしました。専用線は敷設までの納期や最低利用期間等を考えると引き直しのハードルがやや高いのですが、それは許容する事にしました。

私の考えたマルチクラウドネットワーク+オンプレのロケーションの理想は、 AWS/GCP両方が同一DCで構内接続可能なEquinix or @Tokyoにラックを借りてルータを置き、両パブリッククラウドと構内接続する、サービスラックはそのDCに置くです。そうすれば全て構内配線で接続出来るので、回線周りのコスト・納期やレイテンシ等で優位性があります。

全て同一DCに収めるイメージ

全て同一DCに収めるイメージ

オンプレ-クラウド間のネットワークの繋ぎ方

次にオンプレ-クラウド間のネットワークをどうやって接続するのかを考えてみました。
要件でも挙げた通り、DeNAの特徴として サービス数(アカウント数/VPC数)が非常に多い ので専用線経由で多VPC接続という事を考慮する必要があり、 AWSの専用線ネットワークについて調査を進める中で、AWS DirectConnectのある制限に引っかかる事がわかりました。2018年検討当時、DirectConnect経由でVPCに接続できるのは50本までという制限がありました。(※今も制限ありますが、今はDirectConnectGatewayの仕様変更により50以上接続できます) DeNAのアカウント数は余裕で200を超える事が想定されていたので、この制限数の50に引っ掛かります。世の中のワークアラウンドを調べても、VPC内にルータインスタンスを立ててルーティングさせる、という非常にクラウドっぽく無いノウハウが散見されました。せっかくのクラウドなのでオンプレのようなネットワーク運用はしたくありません。

AWSの場合

DeNAではAWS Office Hourという毎週AWSの人が来社して気軽に相談出来るSpecialなmeeting timeが提供されていたので、早速AWSの人にカジュアルに相談を開始しました。タイミングが良かったのか、相談開始して程無くAWS Transit Gateway(以下TGW)という新ネットワークサービスが始まる、という事をUnderNDAで教えてもらいました。

※201907現在TGWはGAですが、東京リージョンのDirect Connectではまだ利用できません

TGWの仕様を細かく調べると多少課題は見つかったものの、概ね我々のやりたい事は出来そうでした。簡単に説明するとTGW=マネージドルータで、オンプレからTGWに接続すればTGWが各VPCにルーティングしてくれます。

TGWはこんな接続イメージです

TGWはこんな接続イメージです

AWS TGWの特徴は下記の通りです。

  • AWSサービスのマネジメントルータ
  • (TGWの)冗長性を気にする必要は無い
  • ルータなので経路情報を持つ事が出来る
  • TGWから他AWSアカウントのVPCに対して接続可能
  • TGWはデフォルトルートを持つことが可能
  • AWS Internet Gateway (IGW)と接続可能
  • AWS Direct Connect Gateway (DXGW)経由でオンプレと接続可能
  • ip addressの重複は不可
  • IPv6利用可能
  • TGWの帯域等はかなり大きく、ボトルネックにならなそう

TGWが無い場合のオンプレ-多VPC間の接続は大変だと思います。 VPC間通信のみを考えてもVPCピアリングやPrivateLinkというVPC間を繋ぐサービスもありますが、どちらも弱点があり今回の設計ではスコープから外しました。各VPC内のインスタンス上にルータをにたててルーティングさせるという前述の方法を採用すれば目的は達成できそうですが、そのルータの冗長化やネットワーク運用を考えると辛いです。

TGWの仕様を調べる中で課題がいくつか見つかったので、課題についてEBC (Executive Briefing Center)というAWSのサービス開発チームの人との個別セッションの場をAWSの方にセッティング頂き、DeNAの要望をAWSの開発の人に余す事無く伝えきる事が出来ました。まだ一部要望した機能は実装されておりませんが、サービス開発のロードマップには乗っています。
2018年 AWS EBC 集合写真(シアトル)

2018年 AWS EBC 集合写真(シアトル)

DeNAでは、AWSとオンプレのネットワーク接続にTGWを採用する事を決めています。

GCPの場合

並行してGCPも調査していましたが、やはりGCPもAWS同様専用線経由のProjectとの接続数制限がありました。 GCPもAWS同様GCP Office Hourという素晴らし(ry、GCPの人に相談したところ、GCP Shared VPCというサービスがある事がわかりました。
AWSのTGWとはかなり毛色が違い、ある1Project(Host Project)から他のProjectに対してVPCをシェアする、というネットワークサービスです。既に利用出来る状態だったので早速調査&検証を開始しました。

SharedVPC.png

SharedVPC接続イメージです

特徴は下記の通りです。

  • (TGWのような)ルータサービスでは無い
  • Host Project によって作成された1つの VPC を複数の Project で共有するサービス
  • Route, Firewall, Subnet, VPN を Host Project から一元管理できる
  • Organization 内で SharedVPC が有効化された Project が Host Project となる
  • Host Project に Attach した Project(Service Project)が共有されたリソース内でGCPサービスが利用できる(GCP全サービスではない)
  • Service Project 固有のスタンドアローンな VPC やその他のサービスも今で通り利用可能

大体やりたい事が出来る事はわかりましたが、TGWと違ってややイレギュラーな思想の為、TGWとは全く違う課題がいくつか見つかったのでGCP Office Hourの場で改善希望を伝えました。

DeNAでは、GCPとオンプレのネットワーク接続にShared VPCを採用する事を決めて、既に利用を開始しています。

その他考慮するポイント

ネットワークアドレスの管理、ネットワークセキュリティを考慮する必要があります。

ネットワークアドレス設計

当たり前の話ですが、オンプレ-クラウド間・VPC間でNATを介さずに自由に通信させたい場合は、オンプレ・クラウド全てでプライベートIPアドレスを重複しないように管理する必要があります。 クラウドだからと自由にアドレス採番をされてしまうと、 オンプレとネットワークを繋げる事が出来ないケースが出てきます。

※アドレスの設計詳細はセキュリティの都合上お伝えできませんが、書ける範囲で書きます

例えば10.0.0.0/8のプライベートアドレスがすべてクラウドで使えるとしても、前述の通りDeNAはサービス数が非常に多いので、全サービスが/16を使いたいと言い出した場合、本当にあっという間にアドレスが枯渇します。 また、そもそもDeNAはオフィス・社員数が多いので、オフィス用のプライベートアドレス空間は大きめに確保しています。また、データセンターにも数千台規模のサーバ群があるので、こちらもvlan毎に大きめにアドレス空間を確保しています。なので、プライベートアドレスは決して潤沢とは言えません。クラウド側にフルにプライベートアドレスを割り当てられるという事はありませんし、今後AWS/GCP以外にも利用クラウドが増えていく事を考えると、プライベートアドレスの管理はしっかりと運用していく必要があります。 クラウド側は耐障害性を考えてAZを分ける等の対応を取る必要があるので、/24ではほぼ足りず、規模に応じて/22-/20の間で各サービスに採番する事にしました。

その他にも細かいアドレス採番ルールもあるので、プライベートアドレスはIT基盤部で採番管理する事にしています。

ネットワークセキュリティ

実はネットワークセキュリティの検討については一番時間を使ったかもしれません。 現在オンプレ環境ではオフィス-DC間、DC内のvlan間でフィルタを設定していますが、このセキュリティ設定管理が非常に煩雑なのでここをどうにかしたいと考えていました。

その中でも、社内の人間のサービスリソースへのアクセス制限の管理が特に煩雑なポイントでした。 机上で考えるとただ決められたセキュリティルールに基づき初期設定すれば良いだけと思えますが、実際はかなりのイレギュラーケースが発生し、毎日のようにフィルタ設定の変更をする必要が出てきます。開発環境-本番環境間のフィルタも同様で、恒常的にフィルタ設定の変更をする必要があります。

vlanベースでのフィルタの設定にも限界がありました。具体的には、例えばサービスを複数跨ってアクセスするような人がいる場合、vlanベースだと人はvlan Aかvlan Bのどちらかにしか所属してしないのでどちらかにかアクセス出来ません。必要であれば最悪その人の為だけにわざわざ新しいvlanを作成する必要があり、vlanベースの通信制御は破綻していきます。 また、サービス=部署単位では無いので、部署単位でvlanを作ってもその通りにアクセス制御をする事も出来ません。

つまり、やりたい事としては、

  • サービス単位のグループで通信制御
  • 人はグループを跨ってもOK
  • そのグループのメンバーはサービス側で管理する

です。
DeNAではGSuiteを利用している為、GoogleGroupでグループ作成する事が一般的でAD連携が出来る仕組みもあったので、GoogleGroupを活用したアクセスの仕組みを考えました。色々と考えましたが、オフィスからもリモート(自宅)からも同一のセキュリティポリシーでアクセスさせたい事を考えるとVPNで制御するしかないかな、という結論に至りました。いくつかのVPN製品を調べていく中で、これも2018年検討当時に AWS ClientVPN がリリースされるという情報をAWS OHで教えて貰いました。(UnderNDA)
既にリリースもされているのでご存知の方も多いと思いますが、特筆すべきはADのSIDベースで制御できるという部分で、ここがマッチしそうでした。リリースされてから色々と触った所、いくつかの課題が見つかりました。

  • フィルタの順序が変えられない(順序を変えるには全消しして入れ直さないといけない...)
  • SIDを同時に複数指定出来ない
  • denyが書けない
  • クライアントのデバイスが固定出来ない

実際に使ってみて頂くとわかると思いますが、通信制御周りが使いづらいです。 上記に挙げたものは全て重要で、改善要望をあげています。

その他にもネットワークセキュリティ設計として、ネットワークセキュリティポリシーの設計をしました。ここの詳細はセキュリティの都合上割愛します。ポイントとしては、クラウド側のVPCのセキュリティポリシーを誰がどうやって制御するのか、具体的にどういうセキュリティポリシーにするのかといった内容で、セキュリティ部の人達と議論を重ねて大枠を決めていきました。

最後に

スケジュール感としては、2018年8月頃から検討開始、その後2018年内は毎週のように議論を重ねて、設計の大枠が出来たのが2018年末だったと思います。その後は細かい微調整やクラウド側への要望を続けたり、という事を継続しています。

この文章を通して、DeNAのオンプレとクラウドとのネットワーク接続への考え方が伝わればいいな、と思います。

色々と相談に乗って頂いたAWS/GCPの方々、どうもありがとうございました!
今後も引き続きよろしくお願いします。

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

DeNA の QCT マネジメント

DeNA の土屋と申します。2014 年に新卒で入社して以来一貫して IT 基盤部に所属し,現在は第四グループのマネージャーとして,全世界向けに提供している超大規模ゲームタイトルおよびゲームプラットフォームのインフラ運用をリードしています。

IT基盤第4G.png

私のグループでは直近 1 年間,クラウド環境におけるコストコントロールの施策中心に,インフラの QCT (Quality, Cost, Time) マネジメントに取り組んで来ました。その具体的な方法や成果については先日の AWS Summit Tokyo 2019 で発表させて頂きました (以下が発表資料です)。

この資料だけを見ると極めて体系的かつ順調に各施策を進めることができた,ともしかしたら見えるかもしれませんが,実際は様々な試行錯誤がありました。本記事ではそのような苦労話に触れつつ,インフラコスト 60% 削減達成までの道のりについてお話しします。

インフラに求められる QCT とは

インフラノウハウ発信プロジェクト第 1 回目の記事でも触れられていた内容ですが,具体的な話に入る前にインフラの文脈における QCT がどのような内容を指すかについて改めて整理したいと思います。

  • Quality
    • 最高品質のインフラを提供することを指します。インフラ起因による障害をなくすことはもちろん,品質維持・向上のためにできることは全て行い,工数の許す限り稼働率 100% を維持し続けることを目指してインフラを運用しています。
  • Cost
    • インフラ費用を適切にコントロールすることを指します。DeNA のインフラ部隊は創業当時から非常に高いコスト意識を持っています。ただし,ただ安ければ良いという考え方ではありません。管理工数や利便性などに照らしてメリットがあるサービスや商品についてはたとえ割高であっても利用するという判断をします。
  • Time (Delivery)
    • インフラ提供のリードタイムを短くすることを指します。品質とコストを求めるあまり,インフラ提供までに何ヶ月も掛かるような状態では DeNA の事業展開スピードを支えられません。どのようなスケジュールでも安定したインフラを提供することも重要なミッションの 1 つです。

本記事ではコスト削減がメインの話題になりますが,他二つの水準を落とさないということは大前提になります。

なぜコストコントロールが必要になったのか

DeNA はもともとメインのシステム基盤にはオンプレを採用していましたが,私のグループが担当するサービスは全世界向け,そして必要リソースが大幅に乱高下するため,サービス開始当初から 100% クラウドで運用していました。

そのような中,2018 年 6 月に大きな意思決定がなされます。全サービスのシステム基盤をクラウド化するという決定です。当時の DeNA インフラの概況や,クラウド化の経緯についての説明は以下の資料に譲り,ここでは割愛します。

オンプレ環境において,DeNA のインフラは圧倒的な低コストを実現していました。これは同一スペックのサーバを同時に大量に購入して調達価格を下げるという工夫や,サーバのリソースを使い切るための技術およびノウハウによるものです。

この状態からクラウド環境へ移行すると費用はどのくらい増えるかを試算したところ,何も工夫せずに移行するとなんと 3 倍にまで膨れ上がるという結果になりました。何も工夫せずと書きましたが,前述のオンプレ環境における技術やノウハウを適用してもなおこのくらい費用が増えてしまうという状況でした。

そこで,このコスト差を埋めるためにコストコントロール施策の実証が必要になった訳です。検討ではなく,実証です。この時点で候補となるような施策はいくつか挙がっていましたが,それらの施策は本当に実現可能なのか,コストは下げられたが品質が劣化することはないか,運用工数が増えることはないか,などの懸念は実際にやってみなければ払拭できません。クラウド移行がすべて完了した後になってやはり実現は無理でした,という事態は何としても避けなければなりませんでした。

システム基盤のクラウド最適化や移行スケジュールの精緻化など,クラウド化の準備として行うべきことは他にもたくさんありましたが,このコストコントロールの実証はすでにクラウドを用いてインフラ運用を行なっている私のグループで担当することになりました。

机上の計算では 50% の削減が可能

クラウドならではのコスト削減はクラウド化の全社決定がなされる前から様々検討はしていました。本腰を入れて進めるにあたり,まずは考えられる手段を列挙し,それらが仮にうまくいった場合に現状のコスト構造に照らしてどの程度のコスト削減が見込めるか,という試算を行いました。その際,阻害要因となるものも一緒に洗い出しています。

例えばオートスケーリング。担当のシステム構成においては,クラウド費用全体の 90% が IaaS の費用であり,その内 60% 程度がオートスケーリングを適用できるサーバでしたので,オートスケーリングによってこれらのサーバの費用が 20% 減らせると仮定すれば全体の費用は 0.90 * 0.60 * 0.20 で約 10% 程度全体の費用を下げられる,というような試算をしました。また,阻害要因としてはオートスケーリングの仕組みを作るための実装コスト,安定して稼働させるまでの導入コスト,またインフラ運用メンバの工数不足などを挙げていました。

他の施策についても同様に試算を行い,全てうまくいった場合 50% の費用削減が可能であるという皮算用を得ました。

ここで重要なのは,もちろんコストを 50% 程度は下げられそうという試算結果も大事なのですが,どの施策から進めるのが費用削減効果と実施工数両方の観点から良さそうか,という優先順位を決めることです。通常のシステム運用をこなしながらこれらの施策を進めていく必要がありましたので,全部を一気に並列に進めることはできません。この優先順位を決めるにあたっては,コスト削減効果の精緻な見積もりよりも (そもそも実際にやってみないと推測の域を出ないので,精緻な見積もり自体不可能),だいたいのオーダ感とどのくらい大変そうかという工数感の方が必要です。

ただし,数値の算出根拠だけはきちんと説明できるようにしておくべきです。先のオートスケーリングの例では 90%,60%,20% という数値が出て来ましたが,90% と 60% は事実なので定数,20% の値は実際のシステムのワークロードに大きく依存するので変数ということになります。こういう説明ができると各施策の勝算の確度が分かるので,優先度を決めるための判断材料の 1 つになりますし,どのくらい未確定要素を含んでいるのか,という認識を関係者間で正しく合わせることにも役立ちます。

オートスケーリングを最優先で導入

コスト削減試算の結果,効果が最も大きかったのはやはり API サーバへのオートスケーリング導入でした。実はオートスケーリングは突発的なトラフィック急騰時のサービス品質への備えとして自動スケールアウト機能だけはすでに導入できていました。また,時間指定の台数の増減もバッチ処理により何とか動いていました。

しかし,これらの既存の仕組みは相当に辛い状態でした。インスタンスの起動,削除,必要台数の計算などオートスケーリングに必要なあらゆる処理が 1 つの巨大なモジュールに全て記述されており,ここに自動スケールインのロジックを追記したり,各処理に対する冪等性担保のための処理やリトライ,エラーハンドリングを入れていくことはもはや不可能でした。台数の自動増減も非常に原始的で crontab に以下のような記述があるだけでした。

 0 21 * * * api-server-service-in  --ammount 50 # API サーバを 50 台追加
 0  0 * * * api-server-service-out --ammount 50 # API サーバを 50 台削除

そこで,既存の資産は活用しつつオートスケーリングの仕組みを抜本的に改善することにしました。この改善施策自体はクラウド化が決定する前の段階から進めており,これから本番環境に導入していくというフェーズでした。刷新後のオートスケーリングシステムの設計や実装の詳細は今後の記事で他メンバから紹介してもらおうと考えていますので,この記事では本番に導入していく過程や,今現在どのように運用しているかをお話ししたいと思います。

本番環境で実際に運用するにあたっては,何よりもまずは安定性を重視しました。API サーバはゲームサーバの根幹を担うものであり,これの台数管理を司るオートスケーリングシステムを安定して稼働させることは大前提です。そうはいっても 1 日に数百台というサーバを作っては壊しという作業をひたすら行うため,どこかしらで必ず不具合が出てきます。

そのため,まずは各処理に対してきめ細やかにエラーハンドリングやリトライを入れていきました。例として対象サーバに対して ssh 接続できるか確認する,という処理の設定ファイルの一部を以下に示します。詳しい書式や定義はここでは省略するとしてリトライ間隔,リトライ回数,タイムアウトが指定できるようになっていることがお分かり頂けると思います。

 [Task]
 Name = "CheckNetowrkSSH"
 Description = "check network connection SSH"

 [Task.Component.Params]
 RetryInterval = 5
 Retry = 30
 Port = 22
 Timeout = 3

また,実際に ssh 接続確認を行うコードは以下の通りで,当然ではありますがきちんとエラーハンドリングを行うコードになっています。

 func checkTCP(target string, timeout time.Duration) error {
     conn, err := makeConnection("tcp", target, timeout)
     if err != nil {
         log.Error(
             "Failed to make a connection.",
             zap.String("protocol", "tcp"),
             zap.String("target", target),
             zap.Error(err),
         )
         return err
     }
     defer conn.Close()
     return nil
 }

続いて,突発的なトラフィック急騰への対応も必要になります。ゲームサーバというものはイベントの開始・終了のタイミングや,日跨ぎ処理のタイミングでトラフィックがわずか数秒間に 3 倍にまで増えるということが毎日起きます。スケールアウトの高速化の工夫は色々と行いましたが,どれだけに高速化してもサーバ起動からサービス投入までに 3 分は要します。

そのため時間指定でのサーバの増設も必要になります。この機能もバッチ処理ではなくオートスケーリングの機能として折り込みました。現在は以下 (10 分間に 20 台ずつ,計 40 台増設する例) のようにコードで設定を書くようになっていますが,将来的には GUI を用意し,インフラメンバだけでなく開発者やビジネスサイドのメンバにも開放し,事前のキャパシティ増設をワンストップでできるようにしたいと考えています。

 launch => {
     amount        => 40,
     time_settings => [
         {
             from   => '05:50:00',
             to     => '05:59:00',
         },
         {
             from   => '06:00:00',
             to     => '06:09:00',
         }
     ]
 }

さて,オートスケーリングの安定化のための工夫をご説明してきましたが,想定外の出来事はどうしても起きます。そういう事態に備えて,オートスケーリングの不具合を早期に検知できるような監視を導入し,問題を検知したらインフラメンバにオンコールで通知することによって 24 時間 365 日即時対応できるような体制を整えています。ここまで至ることは稀ですが,仮に手動対応が必要になったとしても次回以降同じ問題が発生しないようにシステムを改修するということを繰り返し,現時点では相当高品質なシステムに仕上がりました。

最後に実際にオートスケーリングが稼働している様子をお見せします。図 1 からサーバ台数は日々大きく変動していることが分かる一方,図 2 からは CPU 使用率はほぼ一定となるように制御されていることが分かると思います。この結果,最終的に API サーバの費用は 30% 削減することができました。また,これまで手動で台数調整を行ってきましたが,その手間が不要になりましたので運用工数の削減にも大きく寄与しました。

図 1. サーバ台数 (IP Address 数) の推移

図 1. サーバ台数 (IP Address 数) の推移

図 2. API サーバ 1 台の CPU 使用率の推移 (赤: スケールアウト水準,青: スケールイン水準)

図 2. API サーバ 1 台の CPU 使用率の推移 (赤: スケールアウト水準,青: スケールイン水準)

スポットインスタンスへの挑戦

オートスケーリングの導入に加えて,インスタンスタイプの最新化なども一段落したタイミングで基盤が AWS のシステムにおいてはスポットインスタンス導入を進めていくことなりました。2018 年 8 月の下旬頃から仕様確認や実サービスへの導入方法の検討を本格的に始め,その 2 ヶ月後には実際に運用を開始していましたので,かなりのスピード感で進めていたことがお分かり頂けるのではないでしょうか。

実は計画当初,スポットインスタンスの活用は考慮に入れていませんでした。存在自体は認識していましたが,突然シャットダウンされるという話を聞いただけで,最高品質のインフラを提供するための基盤として利用する選択肢には入れられない,入れられたとしても負荷試験用途やワンショットのバッチ処理などサービスに直接影響が及ばない範囲内で,という考えが正直なところありました。しかし,オンデマンドインスタンスと比較すると非常に安価に利用できるという点はやはり無視できず,このタイミングで俎上に載ってきました。

実際に仕様確認を進めていくと,スポットの中断通知から実際にシャットダウンされるまでの 2 分間で完全にサービスから切り離すことさえできれば,あとは通常のインスタンス故障時の対応を自動化だけで品質を落とすことなく運用に落とし込めるということが分かりました。実際に導入を担当したメンバが「スポットインスタンス は高頻度で故障するインスタンスであると考えれば良い」と話をしていたのが印象的です。

また,オートスケーリングに比べて導入できる対象サーバが多いという点もスポットインスタンスの魅力でした。オートスケーリングの導入対象は,台数が非常に多いこと,負荷の変動が大きいこと,スケーリングの判断基準が明確であること,という大きく 3 つの条件を満たす必要があります。これらを満たさないと十分なコストメリットが得られず,オートスケーリングシステムの管理工数の方がかえって高くつくためです。一方スポットインスタンスであれば,ステートレスなサーバに対しては前述の仕組みさえ整えればコストメリットを得ることができます。最終的には API サーバだけでなく,Cache サーバや非同期処理用の Daemon サーバなどステートレスサーバのほぼ全てに対してスポットインスタンス を導入できました。

インスタンス管理の自動化はオートスケーリングのシステムでほぼできていましたので,2 分以内のサービスからの切り離し処理を教科書通りに実装し,まずは数台実際にサービスに投入してみるというところから導入を開始していきました。

ここから先,スポット率をどう上げていくかというところが一苦労でした。最終的にはセカンダリ IP を活用することで図 3 のように使用するプール数を大量に増やし,安定した運用に落とし込むことができたのですが (詳しくは冒頭の AWS Summit Tokyo 2019 の発表資料にまとめていますので,そちらをご参照下さい),そこに至るまでには様々な試行錯誤がありました。

spot-instance-pool.png

図 3. プールごとのスポットインスタンス数

例えば,異なるインスタンスタイプを混ぜて使うための方法は初めから MyDNS とセカンダリ IP で行こうと決められたわけではありません。インスタンスタイプごとにロードバランサも分け,DNS で重み付けすることよってトラフィックのコントロールを行う方法を検討したり,クラウドベンダに対してロードバランサの重み付けラウンドロビンの機能追加リクエストを出したりもしました。

また技術的な課題だけでなく,スポット率を上げて本当に問題ないかという懸念に対して問題ないと示す必要もありました。幸いにもこのケースにおいてはサービス影響が出る確率を評価することができたため,その定量評価によって関係者全員が納得した状態でスポット率を 100% に設定することができました。リスクの伴う変更を行う際はインフラチームのみの判断ではなく,開発者はもちろん事業部の方々にも十分説明をした上で進めることが重要です。

導入当初は 10% 程度だったスポット率も最終的には 100% まで引き上げることに成功し,ステートレスサーバのコストは 60% 削減することができました。また,スポット中断による障害は起きていないという点も特筆事項だと思います。

spot-instance-rate.png

図 4. スポットインスタンス使用率の推移

DB サーバの QCT マネジメント

DeNA が DB サーバとして MySQL を使用していることはご存知の方も多いと思いますが,クラウドにおいても変わらず MySQL を利用しており,なおかつマネージドサービスではなく IaaS 上に MySQL を立てて運用しています。ここでは DB サーバに対する施策について,ゲームサーバにおける DB サーバの負荷傾向について触れながらご説明します。

DB サーバはゲームのリリース当日から数週間程度 CPU,I/O ともに負荷が高い状態となりますが,特に書き込みがボトルネックになり,これをきちんと捌けるかどうかが安定リリースの要となります。この高負荷に対応するため,DeNA ではシャーディング (水平分割) を多用します。タイトルの規模感に合わせて調整しますが,多いものだとシャード数は 3 桁に及びます。また読み込みのスケーラビリティ確保のために,シャーディングされた DB はそれぞれ master-slave 構成を組んでいます。

一方,リリースからしばらく時間が経つとアクティブユーザの数がリリース当初よりは落ち着いてくるため,CPU 負荷も下がっていきます。他方,データは溜まり続けるため,I/O がボトルネックになるという傾向は変わりませんし,ディスクのサイズがネックになることもあります。

このようにリリースフェーズとその後の運用フェーズで大きく負荷傾向が変わってくる DB サーバの運用は非常に難しいです。ステートレスなサーバと違い,単純な台数調整やスペック調整を行うことが難しいからです。特に難しいのがシャーディングした DB のシュリンクです。一度シャードを広げてしまうと,その後の縮小は容易ではありません。しかし,容易ではないからといって放置するわけにもいきません。大量のシャードを運用し続けると膨大な費用が掛かります。したがってシャードの集約が必須になると同時に,どの程度までシャードを集約できるかがそのまま DB サーバのコストコントロールの成果に繋がります。

シャード集約におけるポイントは 2 つあります。

1 つ目は I/O 性能です。シャードを集約していくわけですから,例えば 2 つのシャードを 1 つに集約したら単純に I/O は 2 倍に増えるということになります。ただでさえ I/O がボトルネックであるのに,集約によってその傾向に拍車がかかるため,当然集約後のサーバに対しては高い I/O 性能が求められます。ネットワークストレージをマウントしたインスタンスではこの I/O を捌くのは難しく,私たちはローカルストレージを持つインスタンスを採用することにしました。AWS であれば i3 あるいは i3en インスタンスが,GCP であればローカル SSD をマウントしたインスタンスがこれに相当します。

ローカルストレージは高い I/O 性能を得られる一方で,インスタンスを再起動したり,インスタンス障害などでインスタンスが止まってしまうとデータが全て消えてしまいます。まさに諸刃の剣です。この問題に対しては,データを 4 重に持つことで対応することにしました。すなわち master 1 台に対して slave は必ず 3 台以上用意するということです。また,万一の自体に備えて MySQL のフルダンプを毎日取得し,クラウドストレージへ保存するという仕組みも整えています。

さて,高い I/O 性能は確保できたので次は 2 つ目のポイントである集約方法について考えていきます。普通にシャード集約を行うことを考えると,あるシャードに対して別なシャードのデータを流し込むというのが一般的な方法だと思います。実際に DeNA でも過去この方法を取っていたことがありました。しかしこの方法ではサービスの長時間の停止を伴うメンテナンスが必須になります。他の目的で実施されるメンテナンスに合わせて実施すれば良いと考えられる方もいらっしゃるかもしれませんが,データの流し込みには何時間も要するため,結局シャード集約のためにメンテナンス時間を延長する必要があります。

そこで私たちは 1 つの MySQL プロセスに対してデータを集約していくという方法は諦め,1 台のインスタンスに複数の MySQL プロセスを立てるという構成を取ることでシャード集約を実現しました。この方法は 1 台の物理サーバの性能を使い切るための方法としてオンプレ環境でも多用しています。

まずは 1 台のインスタンスに複数のプライベート IP アドレスをアタッチします。

 $ ifconfig
 eth0      Link encap:Ethernet  HWaddr 02:E1:0C:0A:6E:8A
           inet addr:10.228.155.252  Bcast:10.228.155.255  Mask:255.255.252.0
           inet6 addr: fe80::e1:cff:fe0a:6e8a/64 Scope:Link
           UP BROADCAST RUNNING MULTICAST  MTU:9001  Metric:1
           RX packets:1999977249 errors:0 dropped:0 overruns:0 frame:0
           TX packets:4789699563 errors:0 dropped:0 overruns:0 carrier:0
           collisions:0 txqueuelen:1000 
           RX bytes:2036044138753 (1.8 TiB)  TX bytes:6290789751880 (5.7 TiB)

 eth0:ae49853 Link encap:Ethernet  HWaddr 02:E1:0C:0A:6E:8A
           inet addr:10.228.152.83  Bcast:10.228.155.255  Mask:255.255.252.0
           UP BROADCAST RUNNING MULTICAST  MTU:9001  Metric:1

 eth0:ae49922 Link encap:Ethernet  HWaddr 02:E1:0C:0A:6E:8A
           inet addr:10.228.153.34  Bcast:10.228.155.255  Mask:255.255.252.0
           UP BROADCAST RUNNING MULTICAST  MTU:9001  Metric:1

 eth0:ae49af9 Link encap:Ethernet  HWaddr 02:E1:0C:0A:6E:8A
           inet addr:10.228.154.249  Bcast:10.228.155.255  Mask:255.255.252.0
           UP BROADCAST RUNNING MULTICAST  MTU:9001  Metric:1

 eth0:ae49bd4 Link encap:Ethernet  HWaddr 02:E1:0C:0A:6E:8A
           inet addr:10.228.155.212  Bcast:10.228.155.255  Mask:255.255.252.0
           UP BROADCAST RUNNING MULTICAST  MTU:9001  Metric:1

次に,各プロセスに対する MySQL の設定ファイルを用意し,以下のようなスクリプト (実際に私たちが使っているスクリプトを簡略化して記載) で MySQL プロセスを起動します。

 sub start_mysql {
     my ($hostname, $mysqld_opts) = @_;
     my $pid = fork();
     unless ($pid) {
         close STDIN;
         close STDOUT;
         close STDERR;
         exec "/usr/bin/mysqld_safe --defaults-file=/etc/my-$hostname.cnf $mysqld_opts &";
         exit;
     }
 }

すると以下 (ps auxf の結果を整形) のように複数の MySQL プロセスが起動している状態になります。

 root   /bin/sh /usr/bin/mysqld_safe --defaults-file=/etc/my-ae49853.cnf
 mysql    \_ /usr/sbin/mysqld --defaults-file=/etc/my-ae49853.cnf --basedir=/usr --plugin-dir=/usr/lib64/mysql/plugin --user=mysql ...
 root   /bin/sh /usr/bin/mysqld_safe --defaults-file=/etc/my-ae49922.cnf
 mysql    \_ /usr/sbin/mysqld --defaults-file=/etc/my-ae49922.cnf --basedir=/usr --plugin-dir=/usr/lib64/mysql/plugin --user=mysql ...
 root   /bin/sh /usr/bin/mysqld_safe --defaults-file=/etc/my-ae49af9.cnf
 mysql    \_ /usr/sbin/mysqld --defaults-file=/etc/my-ae49af9.cnf --basedir=/usr --plugin-dir=/usr/lib64/mysql/plugin --user=mysql ...
 root   /bin/sh /usr/bin/mysqld_safe --defaults-file=/etc/my-ae49bd4.cnf
 mysql    \_ /usr/sbin/mysqld --defaults-file=/etc/my-ae49bd4.cnf --basedir=/usr --plugin-dir=/usr/lib64/mysql/plugin --user=mysql ...

後は集約前の master から上記で起動した MySQL プロセスに対してレプリケーションをつなぎ,master を切り替えていくだけです。DeNA では MHA を利用して MySQL の master 切り替えをオンラインで実施できるようにしていますので,メンテナンスなしでのシャード集約が可能です。

この方法を用いることで,DB サーバの数を最大 75% 削減することに成功したタイトルもありました。また,メンテナンスなしでシャード集約を行える方法が確立したことで,今後はリリース初日は DB サーバに十分なキャパシティを持たせるためにあえて最初から数百から数千のシャードを用意しておき,負荷の様子をみながら無停止でシャードを集約していくという運用が可能になりました。

これまでは DB サーバのシャード集約の難しさや維持コストの高さから,必要最低限のシャード数でリリース当日を迎えることがほとんどでしたが,こうしてしまうと万一予想を上回るトラフィックが押し寄せてしまった場合は長期間の障害は免れませんでした。したがって,今回ご説明した運用方法は超大規模ゲームタイトルの安定リリースに大きく貢献し,なおかつコストも速やかに削減することができる非常に優れた方法であると言えると思います。

最後に

今回は主にオートスケーリング,スポットインスタンス,DB サーバの運用に焦点を当ててこの 1 年間の取り組みについてご紹介させて頂きました。QCT 全てを高い水準で満たすために様々な工夫をしているというところを少しでも感じ取って頂けましたら幸いです。技術的な詳細はかなり省略した箇所もありましたが,その内容については今後他メンバからご紹介させて頂く予定ですので,もっと具体的な話にご興味をお持ちの方は引き続き本ブログをご覧下さい。

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

DeNA の AWS アカウント管理とセキュリティ監査自動化

こんにちは、IT 基盤部髙橋です。
DeNA が提供するエンタメ系やヘルスケア系のサービスのインフラを見ており、そのグループのマネージャをしています。
先日 AWS Loft Tokyo で DeNA における AWS セキュリティについて発表してきたので、発表では述べられなかった具体的な設定についていくつか記載します。

presentation.jpg

DeNA の AWS アカウント管理とセキュリティ監査自動化 from DeNA

AWS アカウント管理

AWS アカウント管理を効率的に行うために、スライドの中でいくつかの施策を挙げています。 その中の一部をコマンド例を交えて紹介します。

AWS Organizations を利用したマルチアカウント管理

AWS Organizations の特徴としては以下のようなものが挙げられます。

  • アカウントの一元管理
    • OU (Organization Unit) を用いた階層的なグループ化も可能
  • 一括請求 (Consolidated Billing)
    • アカウントの請求と支払いを統合
  • リザーブドインスタンスの共有
    • 他のアカウントで購入したリザーブドインスタンスを共有することができる
  • サービスコントロールポリシー (SCP) の利用
    • Organization 配下の各 AWS アカウント (メンバーアカウント) の権限を指定できる

また AWS Organizations を利用すればコマンドで AWS アカウントを作成することが可能になります。 連絡先情報やクレジットカード情報は Organization マスターアカウントの情報が引き継がれます。
--role-name には作成されるアカウントへの AdministratorAceess 権限を持つロール名を指定します。デフォルトでは以下に記載している通り OrganizationAccountAccessRole という名前になります。 --iam-user-access-to-billing には IAM ユーザーに請求情報へのアクセスを許可する場合は ALLOW を、拒否する場合は DENY を指定してください。

 aws organizations create-account \
     --email ${EMAIL} \
     --account-name ${ACCOUNT_NAME} \
     --role-name OrganizationAccountAccessRole \
     --iam-user-access-to-billing ALLOW

 

 {
     "CreateAccountStatus": {
         "RequestedTimestamp": 1564398749.614, 
         "State": "IN_PROGRESS", 
         "Id": "car-01234567890abcdefghijklmnopqrstu", 
         "AccountName": "sample-account1"
     }
 }

アカウント作成の状況は以下のコマンドで確認することができます。 State の値が SUCCEEDED になるまで定期的に監視すれば、後続の作業も自動化できます。

 aws organizations describe-create-account-status \
     --create-account-request-id ${ACCOUNT_ID}

 

 {
     "CreateAccountStatus": {
         "AccountId": "123456789012", 
         "State": "SUCCEEDED", 
         "Id": "car-01234567890abcdefghijklmnopqrstu", 
         "AccountName": "sample-account1", 
         "CompletedTimestamp": 1564398812.234, 
         "RequestedTimestamp": 1564398749.696
     }
 }

AWS アカウント作成時の初期設定や設定変更方法の整備

AWS アカウントを作成するときには同時に各アカウントに必要な初期設定もしています。 aws sts assume-role コマンドを利用して設定先アカウントの AdministratorAccess 権限を取得します。 ACCOUNT_ID には上記の出力結果から得られたものを入れます。 --role-session-name は権限を得たロールのセッションの識別子のようなものでここでは適当な値を入れておきます。

 aws sts assume-role \
     --role-arn arn:aws:iam::${ACCOUNT_ID}:role/OrganizationAccountAccessRole \
     --role-session-name "RoleSession1"

上記コマンドで得られた情報から AccessKeyId SecretAccessKey SessionToken の値を以下のように環境変数に設定します。

 export AWS_ACCESS_KEY_ID=ABCDEFGHIJKLMNOPQRST
 export AWS_SECRET_ACCESS_KEY=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN
 export AWS_SESSION_TOKEN=abcdefghijk...<remainder of security token>

まず以下のようにして作成しようとしているロールが存在するか判定します。 ROLE_NAME には作成したいロールの名前を入れてください。

 aws iam get-role --role-name ${ROLE_NAME}

IAM ロールがまだなければ作成をします。 --assume-role-policy-document で信頼関係の内容を記載した json 形式のファイルを指定します。

 aws iam create-role --role-name ${ROLE_NAME} \
     --assume-role-policy-document file://path/to/${ROLE_NAME}-Trust-Policy.json

次に IAM ポリシーの作成をします。 POLICY_NAME には作成したいポリシーの名前を入れてください。 --policy-document でポシリーの内容を記載した json 形式のファイルを指定します。 path/to/${ROLE_NAME}-Policy.json には適切なファイルパスを指定してください。

 aws iam create-policy --policy-name ${POLICY_NAME} \
     --policy-document file://path/to/${ROLE_NAME}-Policy.json

作成した IAM ロールに先ほど作成した IAM ポリシーをアタッチします。

 aws iam attach-role-policy --role-name ${ROLE_NAME} \
     --policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/${POLICY_NAME}

このようにして初期設定に必要なロールやポシリーの作成を繰り返し行います。 その他初期設定として CloudTrail の設定やそのログを保管する S3 Bucket の作成なども DeNA では行っています。

全 AWS アカウントの設定変更に関しては、上記で述べた各アカウントに存在する OrganizationAccountAccessRole を使い、同じように aws sts assume-role をして設定変更を行います。 AWS Organizations を使えば全 AWS アカウントのリストを取得することも可能なので、これで取得できた全アカウントに対して操作するようなものを書けばよいでしょう。

 aws organizations list-accounts

 

 {
     "Accounts": [
         {
             "Name": "sample-account1", 
             "Email": "sample-email@dena.jp", 
             "Id": "123456789012", 
             "JoinedMethod": "CREATED", 
             "JoinedTimestamp": 1536059567.45, 
             "Arn": "arn:aws:organizations::123456789013:account/o-abcdefghij/123456789012", 
             "Status": "ACTIVE"
         }, 
         {
             "Name": "sample-account2", 
             "Email": "sample-email2@dena.jp", 
             "Id": "123456789013", 
             "JoinedMethod": "CREATED", 
             "JoinedTimestamp": 1536059568.45, 
             "Arn": "arn:aws:organizations::123456789000:account/o-abcdefghij/123456789013", 
             "Status": "ACTIVE"
         }, 
         ...
     ]
 }

IAM ユーザー管理

発表の中では社内ディレクトリで管理されるフェデレーティッドユーザーを利用した AWS マネジメントコンソールログインの方法について説明しました。

ID フェデレーションによる AWS マネジメントコンソールログイン

下記は踏み台アカウントから各サービスのアカウントに Switch Role を利用してロールの切り替えを行う図ですが、この時の各アカウントに存在するロールのポリシーと信頼関係について解説します。

aws-management-console-login-by-id-federation.png

踏み台アカウントにある step-service-account1-admin ロールのポリシーは以下のようにしています。

 {
     "Version": "2012-10-17",
     "Statement": {
         "Effect": "Allow",
         "Action": "sts:AssumeRole",
         "Resource": "arn:aws:iam::*:role/service-account1-admin"
     }
 }

踏み台アカウントにある step-service-account1-admin ロールの信頼関係は以下のようにしています。
STEP_ACCOUNT_ID には踏み台アカウントのアカウント ID を入れてください。 SAML_PROVIDER_NAME には作成した SAML プロバイダの名前を入れてください。

 {
   "Version": "2012-10-17",
   "Statement": [
     {
       "Effect": "Allow",
       "Principal": {
         "Federated": "arn:aws:iam::${STEP_ACCOUNT_ID}:saml-provider/${SAML_PROVIDER_NAME}"
       },
       "Action": "sts:AssumeRoleWithSAML",
       "Condition": {
         "StringEquals": {
           "SAML:aud": "https://signin.aws.amazon.com/saml"
         }
       }
     }
   ]
 }

各サービスアカウントにある service-account1-admin ロールのポリシーは以下のようにしています。(ここでは AdministratorAccess 権限となっています。)

 {
     "Version": "2012-10-17",
     "Statement": [
         {
             "Effect": "Allow",
             "Action": "*",
             "Resource": "*"
         }
     ]
 }

各サービスアカウントにある service-account1-admin ロールの信頼関係は以下のようにしています。 STEP_ACCOUNT_ID には踏み台アカウントのアカウント ID を入れてください。

 {
   "Version": "2012-10-17",
   "Statement": [
     {
       "Effect": "Allow",
       "Principal": {
         "AWS": "arn:aws:iam::${STEP_ACCOUNT_ID}:role/step-service-account1-admin"
       },
       "Action": "sts:AssumeRole"
     }
   ]
 }

上記は AdministratorAccess 権限についての設定例ですが、ReadOnlyAccess やカスタマイズした権限ももたせることが可能になっています。

AWS セキュリティ監査自動化

発表では AWS セキュリティ監査自動化システムの実装例としていくつか述べました。 ここではさらにどのようなコマンドを使って判定するかを具体的に書いていきたいと思います。

ルートユーザーに対して MFA を有効化すること

概要: 認証情報レポートでルートユーザーに対して mfa_active の値が true であるか確認します

まずはレポートの生成をします。

 aws iam generate-credential-report

 

 {
     "State": "STARTED", 
     "Description": "No report exists. Starting a new report generation task"
 }

次に生成したレポートを取得します。

 aws iam get-credential-report

取得された内容は Base64 エンコードされているのでデコードする必要があります。 <root_account> の行で mfa_active の値が true であれば MFA が有効であることになります。

 user,arn,user_creation_time,password_enabled,password_last_used,password_last_changed,password_next_rotation,mfa_active,access_key_1_active,access_key_1_last_rotated,access_key_1_last_used_date,access_key_1_last_used_region,access_key_1_last_used_service,access_key_2_active,access_key_2_last_rotated,access_key_2_last_used_date,access_key_2_last_used_region,access_key_2_last_used_service,cert_1_active,cert_1_last_rotated,cert_2_active,cert_2_last_rotated
 <root_account>,arn:aws:iam::123456789012:root,2018-06-20T13:38:27+00:00,not_supported,2019-07-02T09:23:21+00:00,not_supported,not_supported,true,false,N/A,N/A,N/A,N/A,false,N/A,N/A,N/A,N/A,false,N/A,false,N/A
 ...

IAM で利用が完了し使われてないアクセスキーは無効化・削除すること

概要: list-users でユーザ一覧を出し、 list-access-keysStatusActive なものの中で、 get-access-key-last-used から得られる LastUsedDate が特定の期間内であるか確認します

まずは list-users で IAM ユーザ一覧の情報を取得します。

 aws iam list-users

 

 {
     "Users": [
         {
             "Arn": "arn:aws:iam::123456789012:user/sample-user1",
             "UserId": "ABCDEFGHIJKLMNOPQRSTU",
             "CreateDate": "2018-07-23T09:37:10Z",
             "UserName": "sample-user1",
             "Path": "/"
         },
         {
             "Arn": "arn:aws:iam::123456789013:user/sample-user2",
             "UserId": "ABCDEFGHIJKLMNOPQRSTV",
             "CreateDate": "2018-08-01T11:51:47Z",
             "UserName": "sample-user2",
             "Path": "/"
         },
         ...
     ]
 }

次に list-access-keysStatus の情報を取得します。 --user-name には上記で取得できたユーザー名を指定します。

 aws iam list-access-keys --user-name sample-user1

 

 {
     "AccessKeyMetadata": [
         {
             "AccessKeyId": "ABCDEFGHIJKLMNOPQRST", 
             "Status": "Active", 
             "CreateDate": "2019-01-09T08:15:14Z", 
             "UserName": "sample-user1"
         }, 
         {
             "AccessKeyId": "ABCDEFGHIJKLMNOPQRSU", 
             "Status": "Active", 
             "CreateDate": "2019-07-29T16:35:35Z", 
             "UserName": "sample-user1"
         }
     ]
 }

最後に --access-key-id でアクセスキー ID を指定してそのキーの LastUsedDate を求めます。得られた LastUsedDate が特定の期間内であるか確認します。

 aws iam get-access-key-last-used --access-key-id ABCDEFGHIJKLMNOPQRST

 

 {
     "AccessKeyLastUsed": {
         "Region": "us-east-1", 
         "LastUsedDate": "2019-01-09T10:52:00Z", 
         "ServiceName": "iam"
     }, 
     "UserName": "sample-user1"
 }

ELB のアクセスログを有効化すること

概要: describe-load-balancer-attributesaccess_logs.s3.enabled の値が true であるか確認します

aws elbv2 コマンドを使います。 --load-balancer-arn には情報を取得する LB の ARN を指定します。

 aws elbv2 describe-load-balancer-attributes --load-balancer-arn arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:loadbalancer/app/sample-alb1/abcdefghijklmnop

以下のように色々な値が取れます。今回は access_logs.s3.enabled の値を確認します。

 {
     "Attributes": [
         {
             "Value": "false", 
             "Key": "access_logs.s3.enabled"
         }, 
         {
             "Value": "", 
             "Key": "access_logs.s3.bucket"
         }, 
         {
             "Value": "", 
             "Key": "access_logs.s3.prefix"
         }, 
         {
             "Value": "60", 
             "Key": "idle_timeout.timeout_seconds"
         }, 
         {
             "Value": "false", 
             "Key": "deletion_protection.enabled"
         }, 
         {
             "Value": "true", 
             "Key": "routing.http2.enabled"
         }
     ]
 }

RDS でインターネットからのアクセスが不要な場合はパブリックアクセシビリティを無効にすること

概要: describe-db-instancesPubliclyAccessiblefalse であるか確認します

aws rds コマンドを使います。 --db-instance-identifier には DB の識別子を指定します。

 aws rds describe-db-instances --db-instance-identifier sample-db1

PubliclyAccessible の値が false であれば問題ないです。(かなり多くの情報が取れるので省略します。)

 {
     "DBInstances": [
         {
             "DBInstanceIdentifier": "sample-db1", 
             "AvailabilityZone": "ap-northeast-1a", 
             "DBInstanceClass": "db.m5.large", 
             "BackupRetentionPeriod": 7, 
             "Engine": "mysql", 
             "PubliclyAccessible": true, 
             ...
         }
     ]
 }

おわりに

以上、DeNA での AWS アカウント管理とセキュリティ監査自動化について説明してきました。 DeNA ではこれまで以上に大規模に AWS を利用していくために、セキュリティレベルの高い状態をより効率的に構築管理できる方法を日々考えています。

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る

DeNAインフラノウハウの発信プロジェクトを始めます

20190801_blog_1.jpg はじめまして.DeNA システム本部IT基盤部 部長の金子です. IT基盤部は,DeNA全グループの全てのシステム基盤を横断して管理しているインフラ部門です.

DeNAは様々な事業分野にチャレンジし,多種多様なサービスを展開しています. IT基盤部では,その全てのサービス/システムの成功を支えていくために,インフラ部門として,日々様々な技術的なチャレンジを繰り返しています.

  • システムのクオリティ
  • システムのコスト
  • システムのデリバリー

これらの一見,両立/鼎立が非常に難しい3つの要素,この全てで世界最高の水準を達成することが目標です.

  • システムを24時間365日,安定して動かし「続ける」ためには何が必要なのか
  • 数十万リクエスト/秒のトラフィックや,PBクラスのデータを取り扱うには,どのようなインフラ構成が必要になるのか
  • システムの基盤やアーキテクチャはどのような基準で選択すべきなのか
  • システムの運用コストを1円でも安くするためには何をすればよいのか

DeNA創業から20年間,IT基盤部では常にこのような課題にチャレンジし続け,技術的ノウハウを多く蓄積してきました.

また,昨年2018年の6月に,今までシステム基盤のメインとして使ってきたオンプレミス環境を,全てpublicクラウド環境へ移行させることを意思決定し,プロジェクトを開始しています. プロジェクト名は「クラウドジャーニー」,略して「C-Jプロジェクト」と呼んでいます. 現在,このC-Jプロジェクトも順調に進められており,ここでも新たなノウハウが数多く生まれてきています.

このように,技術的なチャレンジを数多く行い,世界にも誇れるノウハウを蓄積してきた IT基盤部なのですが,今まではそのノウハウをあまり多く発信してきてはいませんでした.

そのような中,ここ最近は外部のカンファレンスやセミナーで登壇する機会を頂くことが増え,自分達のチャレンジから得た学びやノウハウを発信してみたところ,非常に多くの方々からご好評を頂き,また多くの方々から詳細についてお問い合わせも頂きました.

20190801_blog_2.jpg

このように多くのリアクションを頂けることは,純粋にエンジニアとしてのモチベーションになります.そしてなにより,この学びやノウハウをより多くの方々と共有し,みなさまに更に使い倒していただくことによって,みなさまと更にエンジニアリングの高みを目指すことができれば,エンジニアとしてこれに勝る喜びはないと思います. そして,DeNAに少しでも興味を持って頂けるキッカケにもなれば嬉しいと考えています.

そこで,本日から来年の4月までの間のほぼ毎週,我々IT基盤部のメンバがそれぞれの業務で得たノウハウを,このDeNAエンジニアブログにて発信していきたいと思います.

担当者の業務都合により,もしかしたら記事を出せない週もあるかもしれませんが,なにとぞ暖かく見守って頂けますと幸いです.

これから約1年間,我々のノウハウをできるだけ多くの方々にお届けできるよう頑張っていきますので,応援のほど,どうぞよろしくお願いします.

20190801_blog_3.jpg

続きを読む
ツイート
シェア
あとで読む
ブックマーク
送る
メールで送る