doridoridoriand’s diary

主に技術的なことを書いていく予定(たぶん)

バックエンドの種類を雑に把握する in AWS

ちょっと調査で使用したので、備忘録がてら記事に起こします。

静的サイトホスティングや、アセット系の保存場所S3を使うことはよくあることだと思います。 ですが、S3の静的サイトホスティングを有効にした状態で直接CNAMEやRoute53のエイリアスレコードを貼ってしまうとキャッシュされず、アクセスされるたびに毎回S3にGETが走ってしまうことになります。

個人のサイトなど低頻度アクセスなサイトであればそれほど問題にはなりませんが、商用サービスなどでは以下の問題が生じることがあります。

  • 転送容量によっては、CDNを経由しないと高額になる(とはいえ月間150TB以上転送したときに目の当たりにする問題だったりしますが)
  • GETリクエストに対する課金がCloudFront経由だった場合より高くなる
  • S3のAPIのRateLimitに引っかかる恐れがある(スパイクアクセスが発生したときに問題が表面化すると思われます)

またそもそも論としてS3は多機能ではありますが、メインの機能はストレージサービスですので、コンテンツ配信は別サービスに任せるのがアーキテクチャー的にも吉です。

ですが、以下理由により現在設定されているレコードのバックエンドが、S3直なのかCloudFront経由なのかを調べられない状況が存在します。

  • AWS CLIを現在叩けるPCではない
  • CLI or マネジメントコンソールに対するアクセス権限が無い

以上に対する対応方法として、簡易的にですが、以下の方法を使用することでバックエンドがS3直 or CloudFront経由かを判断できます。

  • curlでヘッダを見て判断する
  • digで返ってきたIPをnslookupする

curlでヘッダを見て判断する

CloudFront => S3となっているサイトに対してcurlを実行すると以下のような結果が返ってきます。

$ curl -I https://example.com/
HTTP/2 200
content-type: text/html
content-length: 1462
date: Sun, 09 Jun 2019 07:44:59 GMT
last-modified: Fri, 13 Oct 2017 03:20:02 GMT
etag: "ccde8929cea4684f8c27cac3f2060c5f"
accept-ranges: bytes
server: AmazonS3
x-cache: Hit from cloudfront
via: 1.1 d90dc9dxxxxxxxxxxxxxxxxxxxxxx.cloudfront.net (CloudFront)
x-amz-cf-pop: NRT53
x-amz-cf-id: W0WBTwtWRofoio0ZqLSabHmPkbR3GtlPXXXXXXXXXXXXXXXXXX==

このときserverには AmazonS3 と入っており、一見バックエンドがS3直と勘違いしそうですが、

x-cache: Hit from cloudfront
x-amz-cf-pop: NRT53
x-amz-cf-id: W0WBTwtWRofoio0ZqLSabHmPkbR3GtlPlf_0UudiW7caw62BQ048WQ==

とあるように、CloudFront特有のヘッダがついていることがわかります。 それぞれ

  • x-cache: キャッシュのヒット可否及びTTL切れなどの情報
  • x-amz-cf-pop: どのPOPからレスポンスを受けているか
  • x-amz-cf-id: リクエストごとに付与されているユニークなID

となっています。 S3直のドメインにて同様にcurlを打つと、

curl -I https://example.com/
HTTP/1.1 200 OK
x-amz-id-2: INa97Ph1bsyjvUzPVj8jMxJPiON6ZIWnfXcUs30iyaSqJ46vs+xxxxxxxxxxxxxxxxJ+Evb4=
x-amz-request-id: E0000000000000F
Date: Sun, 09 Jun 2019 08:01:12 GMT
Last-Modified: Tue, 24 Jan 2017 11:03:17 GMT
ETag: "0553432f5fbc381066c8aee67a7afadb"
Accept-Ranges: bytes
Content-Type: text/html
Content-Length: 2728
Server: AmazonS3

というヘッダになっておりCloudFrontを挟んだときのような、キャッシュ情報などはありません。

digで返ってきたIPをnslookupする

IPアドレスにはられているDNSレコードを逆引きしてあげることにより、バックエンドがS3かCloudFrontかを知ることができます。

S3がバックエンドの場合、

$ dig example.com +short | xargs nslookup
Server:         127.0.1.1
Address:        127.0.1.1#53

Non-authoritative answer:
000.000.000.000.in-addr.arpa        name = s3-website-ap-northeast-1.amazonaws.com.

Authoritative answers can be found from:

とCNAMEに s3-website-ap-northeast-1.amazonaws.com. と入ってきます。

CloudFrontが間に挟んである場合、

$ dig exapmle.com +short | head -1 | xargs nslookup
Server:         127.0.1.1
Address:        127.0.1.1#53

Non-authoritative answer:
000.000.000.000.in-addr.arpa        name = server-000-000-000-000.nrt20.r.cloudfront.net.

Authoritative answers can be found from:

とCNAMEに server-000-000-000-000.nrt20.r.cloudfront.net. と入ってきます。

以上からバックエンドの種類を判断することが可能です。 実際の障害対応とかでは適切な権限を持ったエンジニアが対応するかと思われるので、上の手法を使うことは無いと思いますが、このような手法でもできないことは無いということをお伝えしたく記事にしました。

(ブログ書く頻度が低すぎて文体が毎回変わってますが、そのうち統一します。。)

Rubyのoptparseの挙動について

もう知ってる人にとっては、当たり前じゃん何言ってるのみたいな内容かもしれませんが、私は最近知ったので。。

Rubyには optparse というコマンドライン引数を読み取るライブラリが標準でついてくる。 これを使ってよくバッチ処理とかに使うスクリプトを書いてたりする。

この前書いたコードで以下のようなオプションを書いた。

OPTIONS = {}
OptionParser.new do |opt|
  opt.on('-n', '--no-daemon') {|v| OPTIONS[:no_daemon] = v}
  opt.parse!(ARGV)
end

デーモン状態で動くほうが通常モードだったアプリケーションなので、デーモン状態でない時を明示的にしたいときにと思い、 --no-daemon とした。 当初はこの引数を以下のように使用していた。

unless OPTIONS[:no_daemon]
  # :no_daemonがfalseだったときの処理(デーモン状態で動かしたいときの処理)
end

しかしどうも期待どおりの挙動を示してくれない(常にデーモン状態になる)のでドキュメントを見た。library optparse

--[no-]...などとすることで、否定型のオプションを指定することができます。

require 'optparse'
opt = OptionParser.new

opt.on('-a', '--foo') {|v| p v }
opt.on('--[no-]bar') {|v| p v }

opt.parse!(ARGV)
p ARGV

ruby sample.rb -a foo bar --bar baz --no-bar
# => true
     true
     **false**                              # <- --no-bar の指定による。
     ["foo", "bar", "baz"]

noってつけると否定形になって、falseが返ってくるのか

いつもTrueの感覚で書いていたので普通にミスった。

つわけで当初書いたここのコードブロックは

--no-daemonオプションあり => false
--no-daemonオプションなし => nil => false

となるので、常にデーモン状態となったわけだった。

最終的に以下のように修正した。

if OPTIONS[:no_daemon].nil?
  # :no_daemonがfalseだったときの処理(デーモン状態で動かしたいときの処理)
end

こっちのほうが素直な実装だとは思う。

corretto8をAnsible経由でインストールしてAmazonLinuxで使う

前回書いたブログの日にちが1月2日だって。もう引くレベル。

個人用途ではOracleJavaのライセンス問題はそこまで気にする必要ないけれども、会社でJavaを使ってサービス作って運営する業務をしている身としては、頭の片隅にいつもいる感じで、ちょっと気持ち悪かったのも事実。

そんなときに登場した Corretto

AWSありがてえ。。

以外の何者でもない感じであった。(まあOpenJDKでいこうってプロジェクトもあるので完全ではないけど)

Jenkinsを立てる必要が出てきて、どうせなら使ってみようと思って、Ansible経由でインストールしてみたので、その過程を忘備録的に残しておく。
※Jenkinsのことを脳死で嫌う人いるけれども、別にそれをCircleCIやCodeShip変えたからモダンだみたいなのも個人的にはよく分からない。まあ確かにCircleCIチョー便利なのは納得しますが。

使用環境はAmazonLinux2。いきなりAnsibleのtask貼っちゃう。

はい。これだけ。AmazonLinuxならこれだけでインストール完了します。

ついでにJnekinsをインストールするtaskも貼っておく。

これらのインストールが完了して、初期設定とか済ませて、本当にcorrettoが使われているのかをJenkinsの管理画面から確認する。

jenkins runs with corretto8
jenkins runs with corretto8

問題なさそう。

ターミナルをカラフルに表示してみる

12月があっという間で本当に光陰矢の如しとはこのとこだなと思いましたまる

一ヶ月強ぶりの更新となった。一ヶ月ぶりの更新なのにまさかの小ネタなのはお許し下さい

ログ出力時のターミナルの表示をもっとリッチにしたいと思い、どの色が使用可能なのかを確認したかったので作った突貫スクリプト
こちらのブログを参考にした

以下gistにまとめた

実行するとこんな感じで表示される f:id:doridoridoriand:20180102214957p:plain

次回はもっとまともな記事を書こう

JMeterのクラスタをゆるく立ててみる

仕事でJMeterクラスタを使う必要が出てきたので、割と簡単に立てられる方法無いかなと思い、実際にやった方法を忘備録的に残しておく。(まあ会社のwikiにちゃんとあるんだけれど)

GatlingやSaasなどが既に沢山ある状況で、なんで昔ながらのJMeterかというと、既にあるシナリオを使いたいというのが最大の理由。 とはいえJMeterが悪いわけでは全く無いので新しい何かにするモチベーションもなかったのも事実。

構築は全部AWS上。スレッド数がそこそこ多いので、Master-Slave構成とした。 東京リージョンを使用して建てたが、負荷が1つのAZに依るのはテスト的にも基盤的にも良くないだろうと考え、2つのAZに均等にまたがって建てるようにした。
Slave側をstop/startする度にパブリックDNSが変わるのも不便かと思い、本当はEIPをアタッチすべきであるが、aws shellを使ってPublicDNS名引けるよなと思ったこと、将来的にはクラスタサイズを自由に変えたいという思いがあったことなどから敢えてEIPは使用していない。

tag:Nameを適切に設定していれば、aws cliとjqを使用して以下のように取得することが可能である。
以下はtag:Nameを load-test-jmeter-slave-1c に設定していた場合の例。

$ aws ec2 describe-instances --filters="Name=tag:Name, Values=load-test-jmeter-slave-1c" | jq -r ".Reservations[].Instances[].PublicDnsName"
ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com
ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com

これが分かればMasterからSlaveに接続する際に困らないはず。

JMeterとJava8のインストールはAnsibleを使用。 JMeterインストールの主要部分を切り出したロールはこんな感じ。

---

- name: download Apache JMeter
  shell: >
    wget -O {{ jmeter.src_path }} {{ jmeter.pkg_url }}

- name: Unarchive Apache JMeter
  unarchive:
    src: "{{ jmeter.src_path }}"
    dest: "{{ jmeter.dst_path }}"
    remote_src: yes

vars配下には以下の内容を記したmain.ymlを配置してある。

## vars for Apache Jmeter
---
jmeter:
  src_path: /usr/local/src/source/apache-jmeter-3.3.tgz
  dst_path: /usr/local/src/script
  pkg_url: http://ftp.yz.yamagata-u.ac.jp/pub/network/apache//jmeter/binaries/apache-jmeter-3.3.tgz

読むと分かるのだがこれはJMeterのインストールにしか使えない。メモリのアロケーションとかその他confをいじった状態で再度Ansibleを流してしまうと綺麗サッパリなくなってしまうので注意。
(本当はちゃんと主要ファイルをテンプレート化して撒く処理を追加するなりすれば良いのだけれど、ゆるくクラスタを立ててみるの趣旨からはそれてしまうので、今回はなしの方向で)

ここまで出来ればとりあえずJMeterをMaster-Slave構成で起動できるようになる。

Masterから実際に繋いでみる。コマンドは以下のようになるはず。

$ ./jmeter -n -t test.jmx -e -l log.jtl \
  -R ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com,ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com,ec2-xxx-xxx-xxx-xxx.ap-northeast-1.compute.amazonaws.com

一応オプションを説明すると、

-n cliモードで起動(本テストでは基本的にGUIを起動しないように)
-t シナリオファイルの指定
-e 実行後のレポート出力
-l 実行後のログファイルの出力
-R 使用するSlaveのエンドポイント

もっとちゃんと設定すると、 このページのような GatlingライクなHTMLでグラフィカルなレポートも出力できたりするが、今回はとりあえず動かしたかったので。 今回はここまで。

CloudFormation忘備録2


前回はただ単体のセキュリティグループを作っただけなので、今回は複数のセキュリティグループを連携させてみる。

例えば図のような経路に対するセキュリティグループを作りたくなったとする。

img-1.png

このときに、それぞれのコンポーネントに対してセキュリティグループを充てる必要が出てくる。 今回の場合ではリクエストを一番最初に受け付けるロードバランサと、そのロードバランサから来たリクエストを処理するAPサーバーの2つとなる。 ロードバランサはSSL終端をしているためHTTPS(443)のみ。APサーバー達はロードバランサからのリクエストしか受け付けないようにする。 (SSH出来ないよとかはとりあえず無視で。それは別のセキュリティグループを作って充てましょう)

前回書いたセキュリティグループのYAMLファイルを流用する。
以下のようにディレクティブを加えた。

---
AWSTemplateFormatVersion: "2010-09-09"
Description: >
  Here is the area of description of this CloudFormation template.
Resources:
  WebAPServersSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: SecurityGroup of webservers
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '3000'
          ToPort: '3000'
          SourceSecurityGroupId: !Ref ApplicationLoadbalancerSecurityGroup
      Tags:
        - Key: Name
          Value: dev-web-ap-sg
      VpcId: vpc-XXXXXXXX
  ApplicationLoadbalancerSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: SecurityGroup of Application Load Balancer
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '443'
          ToPort: '443'
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: dev-alb-sg
      VpcId: vpc-XXXXXXXX

別段変わったところは無く、ALB用のセキュリティグループが増えた形となる。ただしAPサーバー達に充てるセキュリティグループ dev-web-ap-sg には変更が入っている。

SecurityGroupIngressのアクセス元を表す表記が CidrIp から SourceSecurityGroupId へ変更となっている。 このように設定することによって、 dev-alb-sg を充てたロードバランサからのみのリクエスト受け付けるように出来る。 ちなみに今回は参照先・参照元共に一緒に生成しているため !Ref を利用して未知のセキュリティグループIDに対応出来るようにしているが、 既存のセキュリティグループを参照したい場合は sg-xxxxxxxx を書いてあげればおk。

前回のCloudFormationのスタックを利用してupdate-stackを実行。

無事にできていた。

img-2.png

CloudFormation忘備録

前のエントリーから非常に時間が開いてしまった。。
前のエントリーの投稿日60日前とか死ぬしかない。

本当に意識的に書いていかないと全然続かないな。 忙しかったんだけど、まあ酒飲んでる時間とかちょっと削れば1エントリーくらい書けるわけで。 まあ良い訳ですね。


業務で積極的にCloudFormationを使うようにしており、セキュリティグループやらロードバランサもCloudFormationで作っている。 以外とこの辺をCloudFormationで作っている人が居ないようなので、忘備録がてらブログに記しておく。

AWSのドキュメント読めば正直解決するのであるが、デバッグしずらいところでもあるので、このエントリーが誰かの役に立てばと。

いきなりソースコードから。 (not ステーキ)

---
AWSTemplateFormatVersion: "2010-09-09"
Description: >
  There is the area of description of this CloudFormation template.
Resources:
  WebAPServersSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: SecurityGroup of webservers
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: '80'
          ToPort: '80'
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: dev-web-ap-sg
      VpcId: vpc-XXXXXXX

最小構成のセキュリティグループのCloudFormationテンプレートを書いた。

おおまかな構成としては

AWSテンプレートフォーマットのバージョン
このテンプレートの説明
テンプレートで定義しているリソース

となっている。意外と構成要素少ない。なので、リソース配下にどのようなリソースをおけばよいかを書けば基本的には完成する。

一つずつ説明。

WebAPServersSecurityGroup

ここは正直なんと書いても問題ない。これ自体が変数みたいなもの。 今回は例として、WebAPサーバーにアタッチするセキュリティグループとしたので、このように書いているだけ。

Type: AWS::EC2::SecurityGroup

これはどのようなリソースをこのディレクティブ内部で使用するかを定義するもの。これはAWSのドキュメントを調べないとわからないので各自で。

Properties:

この下にネストさせて、色々構成物を定義していく。

GroupDescription: SecurityGroup of webservers
SecurityGroupIngress:
  - IpProtocol: tcp
    FromPort: '80'
    ToPort: '80'
    CidrIp: 0.0.0.0/0

GroupDescription

セキュリティグループの説明文。事業部では英語で書くことが決まりとなっているが、別に日本語でもエラーにはならないと思う。

SecurityGroupIngress

インバウンドのルールのこと。ちなみにアウトバウンド(EC2 => ネット)の方は SecurityGroupEgress。だた、EC2にアタッチするセキュリティグループでアウトバウンドを制限する要件はそれほど高くないと思われるので、今回は割愛。
複数定義したいときは次のように書くことも出来て、

 - IpProtocol: tcp
   FromPort: '80'
   ToPort: '80'
   CidrIp: 0.0.0.0/0
 - IpProtocol: tcp
   FromPort: '443'
   ToPort: '443'
   CidrIp: 0.0.0.0/0

ちなみに更に一気に範囲を指定してあげたいときは

- IpProtocol: tcp
  FromPort: '2000'
  ToPort: '4000'
  CidrIp: 0.0.0.0/0

とすることで 2000~4000番ポートを一気に開けることが出来る。 完成したCloudFormationテンプレートを実際に使用したい時は以下のコマンドを使用する。
(AWS CLIの基本的な使い方はマスターしている前提で話しています。そもそもAWS CLIってなんぞやって方はこちらを参照ください)

$ aws cloudformation create-stack --stack-name=dev-web-application-security-group --template-body=file://dev-web-ap-sg.yml

YAMLファイルを dev-web-ap-sg.yml として保存して、この構成名を dev-web-application-security-group として作成する感じ。

実行すると以下の結果が返ってくる。

/Users/dorian/sandbox aws cloudformation create-stack \
                      --stack-name=dev-web-application-security-group \
                      --template-body=file://dev-web-ap-sg.yml | jq -r
{
  "StackId": "arn:aws:cloudformation:ap-northeast-1:0123456789:stack/dev-web-application-security-group/8d7bf440-c6e9-11e7-931f-500c44f24c1e"
}

マネコンを開いてみると

ててーん。あっという間に完成。実際にセキュリティグループを見てみると

確かにできている。ちなみにCloudFormationの画面を確認してみるとこんな感じで出ていた。

最初に頑張って書いてしまえば、インフラ構成をgitでバージョン管理出来たりするので変更履歴置いやすいし、インフラエンジニアじゃなくてもどのような構成になっているのかを分かってもらえるので変なコミュニケーションが発生せず快適だったりする。