Auto ScalingでEC2にElastic IPを設定していたら結果的に色々な事が学べた話

OGP

はじめに

こんにちは。SRE部USED基幹インフラの丸山です。

ZOZOUSEDでは2018年、当時社名がクラウンジュエルからZOZOUSEDに変更になるころからAWSの利用を開始致しました。当時はオンプレミス環境が多く、AWSの導入については画期的ではありましたが苦労も多かったとも聞いています。上記AWSで構築した環境について、前任者の異動に伴い私をはじめ他のメンバーで引き継ぐ事になりました。

当時私共はいわゆる情シスとして社内及び倉庫内のオンプレミス環境のインフラを管理する業務が多く、AWSについてはほぼ初心者の状態でした。そのため書籍をあさったり、Webの記事を検索したりと手探りで運用や改善を行っておりました。今回はAWS初心者だった私がタイトルの案件を通じてAWSのいろいろな機能に触れる経験ができたお話をしたいと思います。主にAWS初心者の方などにお役にたてばと考えております。

実装の背景

前任者から引き継いだAWS環境は大きく2つあり、そのうち1つの環境についてはまた別の部署で管理をしておりました。そちらに関してはほぼインフラ担当者が不在の状況で、私共から見てもブラックボックスの状況に近いものがありました。あらかじめ分かっている状況は以下の通りでした。

  • 毎週必ずEC2、RDSなどインフラに負荷が急激にかかる時間帯があるが、サービスの提供においては問題が発生したことはない
  • 上記の時間帯においてもEC2及びRDSのスケールアウトが発生するほど負荷が上昇したことは過去に一度も発生したことがない

ある日、このAWS環境で「毎週必ず負荷が急激にあがる時間帯」において急激にアプリケーションが大量のエラーを出力し、サービスを提供できないような状態になってしまったとの連絡を担当の開発者から受けました。そのトラブルは、応急処置にて一旦は解消したもののその時点では根本的な原因究明には至りませんでした。

当時全く原因が分からない状況で、あらゆる方向から調査を行いたいとの強い要望がありました。開発担当者からも「原因の切り分けのため一時的にEC2の台数を拡張してテストをしたい」という要望を受け、今回の案件の環境を構築することとなりました。

構成で特徴的だったのはサービスの仕様上「全てのEC2がElastic IP(以降、EIPと呼びます)をアタッチされている必要がある」という点でした。

「NATゲートウェイを使えばいいのでは?」と考える方も多いと思いますが、その点に関しては後述させて頂きます。

元々の構成 依頼の構成
AWS1-1 AWS1-2

要件定義

開発担当と相談して、より詳細な要件を詰めました。

必須項目

  • 10台のEC2は全てEIPを持つ必要がある
  • 業務提携先のオンプレミスサーバと連携しており相手先FWに穴をあける必要がある

  • 現状のEC2はパブリックサブネットに配置してありこの構成を変えたくない

    • 当時の構成であり現在は異なる
  • 開発側の任意のタイミングでEC2を増やしたり減らしたりしたい

AWS1-3

問題・課題点

このようなケースでは本来NATゲートウェイがベストプラクティスです。EC2にEIPを一つずつ持たせるのは費用の面から見ても無駄遣いのようにみえました。

また、そもそもEC2がパブリックサブネット配置という要件がセキュアでないように感じました。なお、現在は改善しています。

そして、EC2 Auto ScalingのもととなるAMIがかなり古いことが分かりました。またスケールアウト時にデプロイするようなこともしていないため、このままでは元々存在するEC2とスケールアウトしたEC2で機能差が発生することが判明しました。

検討結果

まずNATゲートウェイを試してみるために、EC2もプライベートサブネットに設置してみます。

本件とは直接関係ありませんがリリースの都度、Auto Scalingで使用しているAMIと差分が発生してしまう問題に関してはリリースのタイミングを把握し、リリースの都度AMIを作成します。あくまで暫定対応です。

以下のような構成にできればEIPが一つで済みます。

AWS5

NATゲートウェイを試してNGとなった理由

早速NATゲートウェイの構築に着手しました。構築自体はそれほど難しくなく以下のサイトなどを参考にすぐに完了しました。

docs.aws.amazon.com

NATゲートウェイを利用すればEIPはそもそも1個で済みますし、料金、業務提携先のNWの設定の手間暇などの面からもいい事ばかりと考えていました。またEC2もプライベートサブネットに置き換えセキュアにする事ができ、色々な問題を解決したつもりでした。しかし担当者と相談していく中でいくつか問題があることが分かりました。

まず、NATゲートウェイを利用したアプリケーションの動作確認がそもそも何故か失敗しました。またこの問題や理由についてアプローチしている時間が開発側に当時ありませんでした。単純にトラブルシュートで使いたいだけなので大きなインフラ構成の改修はまた別のタイミングで行えば良く、同様の構成を早く用意することが優先されました。

また、NATゲートウェイを利用するとEC2のサブネットが必然的にプライベートになってしまう点も問題として出てきました。そのため直接SSHでログインして作業が必要となる際に、運用上困る場合があるとの意見を頂きました。当時はこれが問題となっていましたが、後述する通り、この問題は類似したことがマネジメントコンソールよりできることが分かりました。

再検討

上記の状況を踏まえて、以下のような方法で進めようと再考しました。

  • EC2 Auto Scaling時にEIPを割り当てる手法が無いかの確認をする
  • サブネットは一旦パブリックのままにし、インフラ構成の改修はまた別の機会に行うこととする
  • 開発担当者が好きなタイミングでEC2増やしたり減らしたりしたいというリクエストについてはマネジメントコンソールから手動Auto Scalingしてもらう
  • AMIについては引き続きリリースの都度作成する

NATゲートウェイ環境(プライベートサブネット)でも直接EC2にアクセスすることは可能

少し余談となりますがプライベートサブネットのEC2についても踏み台サーバなどを利用せず、直接アクセスする方法もあるので紹介したいと思います。

System ManagerSession Managerという機能です。

詳しくは以下URLなど参考にして頂ければと思います。

docs.aws.amazon.com

SessionManager

該当のインスタンスにマネジメントコンソールから目的のインスタンスに接続できます。便利ですね。ちなみに接続時にはssm-userという独自のユーザーが使用されるようです。

以下が実際に対象のLinuxのインスタンスにSession Managerを使用して接続してみた画面です。

SessionManager2

EC2 Auto Scaling時にEIPを割り当てる方法の調査開始

インターネット上の情報を探してみたところ、EIPをEC2に割り当てるシェルスクリプトのサンプルを紹介しているサイトは幾つかありました。そのシェルスクリプトをAWSのどの機能を利用してEC2 Auto Scaling時に実行しているかを紹介しているサイトはこの記事を執筆している時点では少ないように感じました。

実現に向けて

AWSの機能としては提供されていないので自前で作り込む必要があることが分かりました。AWS CLIを利用してシェルスクリプトを頑張って作成すればEIPをEC2に割り当てる事はできそうなので、「Auto Scaling時にどのようにしてそのシェルスクリプトを実行するか」という方法を検討していきます。

AMIの作成

元となるAMIは、前回作成時からどのような変更があったか分からないため新しく作成することにします。リリースが発生する度にAMIを作成するような運用は効率が悪いのですが、こちらに関しては後述させて頂きます。

さて、AMIの作成に関しては「再起動」が前提となっています。前任者から引き継いだ手順書には「再起動は絶対にするな」とありましたのでこれは実は意外でした。今回は再起動が可能な環境だったので問題ありませんでしたが、どうしても再起動できない場合には「再起動しない」というオプションもあるので、そちらを利用する方法もあります。しかしこの方法で作成したイメージのファイルシステムの完全性は保証できないということなので、できるだけ再起動を伴ったほうがいいでしょう。AWSでもこの方法は推奨していないようです。

AMI

つまずきポイント1:CloudTrailによるAMI作成元EC2の追跡

AMIの作成時に困ったことが起きました。普段EC2は2台で運用しているのですが、AMIの取得元がその2台のどちらかなのか、もしくは全く違うEC2から作成したものかが分かりません。マネジメントコンソールから必死に探しますが証跡を見つけることができません。ここで今回役にたったのがCloudTrailです。

docs.aws.amazon.com

CloudTrailの記録から、どのインスタンスからAMIを作成したか突き止めることができました。過去90日分のイベント履歴であれば画面からも追跡できます。AMIのIDから検索する場合は「リソース名」から検索をかけて下さい。

今回はAMI IDからリソース名で追跡し、無事にCreateImageのイベントを特定できました。

AMI2

CreateImageをクリックすると作成元のEC2のインスタンスIDが分かります。

AMI4

つまずきポイント2:Service Quotas

この機能を実行するにはEIPがそもそも足りません。事前にまとまった数のEIPを発行しておく必要があることと、現状何個EIPを保持しているか確認する必要がありました。

しかし、「今いくつEIPを持っていて、いくつ使っているのだろう?」 という疑問の解決方法が分かりませんでした。

この問題は Service Quotasという便利な機能で解決しました。以下のような機能があります。

  • EIPの総数の確認
  • クォーターの設定
  • EIP以外の値の設定や確認
  • 上限緩和申請

aws.amazon.com

実装

いよいよシェルスクリプトの実装です。しかしインターネット上で提供されているシェルスクリプトのサンプルがそのまま自分たちの環境で動くとは限りません。まず手動でAMIから起動したEIPを割り振っていないEC2にてインターネット上にある割当スクリプトを持ってきて実行してみます。見事に失敗しました。

しかし何度か修正しているうちにEC2上から直接実行する形ではEIPを割り当てることが何とかできるようになりました。これをAuto Scaling時に動かすことができればいいわけです。

つまずきポイント3:ユーザーデータ

さてここが肝の部分です。このシェルスクリプトをどこで設定して動かすのか。OSはLinuxなのでAMI作成時に「/etc/rc.local」に上記シェルスクリプトを設定して作成するという方法も考えました。

その方法の調査を続けたところマネジメントコンソールのEC2を起動する高度な設定の中に「ユーザーデータ」という項目があり、この中にシェルスクリプトを直接埋め込めることが可能なことが分かりました。高度な設定はそもそも触ったことがなく画面の1番下に隠れるようにあったので全然知りませんでした。また、それがAMIから起動するときも同様に使用できることが分かりました。EC2上ではシェルスクリプトがうまく動いたので次はAMIを起動する際にユーザーデータ内に先程のシェルスクリプトを埋め込んで上手く動くか検証します。

ユーザーデータ内には以下の様に通常のシェルスクリプトを作成するのと同様に記入可能です。なお、実際に使用したシェルスクリプトは実行できない環境もあるようなので今回は割愛させて頂きます。

#!/bin/bash

# 変数も使用できます
eip_groups="eipalloc-XXXXXXXXXXXXX eipalloc-XXXXXXXXXXXXX ...etc"
# AWS CLI が使用できる環境であればAWSコマンドも実行可能
aws ec2 associate-address ~

.....etc

以下がその画面です。

userdata1

AMIを起動した際にプールしてあるEIPを見事に関連付けることができました。

ElasticIP1

AMIからの検証に成功しました。いよいよEC2 Auto Scalingに対して設定します。

つまずきポイント4:起動設定と起動テンプレート

EC2の画面から「Auto Scaling グループを作成する」という部分をクリックすると以下のような画面が表示されます。特に赤枠内の「起動テンプレート」という部分が気になりました。前任者は「起動設定」から全て作成していて手順としてもチーム内で確立されていたのですが、「起動テンプレート」がそもそもデフォルトになっており、違いが気になったので少し調べてみました。

kidou

AWS公式サイトには以下のように記載がありました。

docs.aws.amazon.com

kidou2

どうやら起動設定の後継の機能が「起動テンプレート」に該当するようです。どちらにも「ユーザーデータ」内にシェルスクリプトを設定することができます。AWS推奨ということもあり、今回は起動テンプレートで挑戦してみることにしました。起動テンプレートには以下のような特徴があるようです。

  • 既に存在する起動設定は全て起動テンプレートに置き換える(コピー)事が可能
  • 起動テンプレートはバージョン管理が可能
  • 起動設定と比較すると設定項目が多く、より細かい設定が可能

起動テンプレートの作成

では実際に「EC2」→「起動テンプレート」より起動テンプレートを作ってみます。

画面1番下の「高度な詳細」を展開しないとシェルスクリプトを設定する「ユーザーデータ」が表示されないのでご注意下さい。

kidou4

沢山の項目がありますが全てを入力する必要はありません。今回私が使ったのは以下の項目だけでした。

  • AMI
  • インスタンスタイプ
  • VPC
  • セキュリティグループ
  • ストレージ
  • ユーザーデータ

また今回検証する中で何度か起動テンプレートを作り直したのですが、以下のようにバージョン管理されていることが分かります。

kidou7

Auto Scaling グループへの設定

作成した起動テンプレートをAuto Scaling グループへ設定します。まず起動設定から起動テンプレートに変更します。

画面右上の「起動テンプレートに切り替える」をクリックします。

AutoScale1

起動テンプレートに切り替わります。ここでは「dev_test」という名称で起動テンプレートを作り、バージョン管理をしています。私の場合は各バージョンでユーザーデータ内に記述するシェルスクリプトの内容などを変更したりしていました。

デフォルト値を変更していなかったので常に「Latest」を選択していました。

AutoScale2

Auto Scalingのテスト

実際にAuto Scaleのテストをしてみます。手動Auto Scalingの設定をします。「Auto Scaling グループ」→「予定されたアクション」の画面からです。

AutoScale3

10台をEIP付きでAuto Scalingさせる想定です。

AutoScale4

10台のEC2が起動して、EIPがアタッチされていることが分かります。

AutoScale

総括

上記手動によるEC2 Auto Scaling方法を開発担当者に簡単に説明し、原因追求に着手します。その後、苦労の甲斐あってトラブルを引き起こすバグの特定・修正に至りました。

しかしそれ以上に今回「EC2 Auto Scaling時にEIPを割り当てる」という作業を行う中でAuto Scalingの設定以外にも今回取り上げたような技術や仕様を学ぶことができ、紆余曲折ありましたが決して費やした時間は無駄ではありませんでした。

  • NATゲートウェイ

  • Service Quotas

  • ユーザーデータ

  • CloudTrail

  • 起動設定と起動テンプレート

  • Session Manager

なお、Session Managerについては今回の作業の中で本格的に使用したわけではないのですが今後使用して行く予定です。

私的には「ユーザーデータ」をその後多用しています。例えばgitでソース管理している場合ですとスケールアウトするときにユーザーデータに「git pull~」のように書いておくだけで最新のリソースが反映されるので毎回AMIを作成することもなく、いろんな場面で重宝しています。またyumコマンドなども使えるので簡単なミドルウェアの構成管理としても利用できそうです。当時リリースの度にAMIを毎回作成して、起動テンプレートに設定して、ロードバランサーに反映するような作業を行っていたころが懐かしいです。

最新のリソースを反映するには書籍などから「CodeDeploy」、構成管理するには「OpsWorks」しかないという思い込みがあったので、ユーザーデータだけで運用がこんなに楽になるとは思ってもいませんでした。

さいごに

SRE部USED基幹インフラでは、本案件後から今回学習したユーザーデータ、CloudTrail、起動テンプレート etc.を利用してより安定して効率のよいインフラのカスタマイズ、ならびに新しい技術のキャッチアップを心がけています。今後、多くのサーバをクラウドに移行する予定もありチューニングや見直しを引き続き実施し、さらなる効率化・安定化を目指しています

ZOZOテクノロジーズでは、一緒にサービスを作り上げてくれる方を募集中です。ご興味のある方は、以下のリンクからぜひご応募下さい!

tech.zozo.com

カテゴリー