ぺい

渋谷系アドテクエンジニアの落書き

Packerで作成されたSnapshotを消す

便利なんだけど放置しがちなAMIたちを消す

f:id:tikasan0804:20180621205204p:plain

Packerはイメージの管理に非常に便利です。弊社でも、AWSのAMIの作成時に使っています。

tikasan.hatenablog.com

ただ、AMIは放置していると無限に作成されてしまうので、いくら安いストレージ料金であっても、結構溜め込んでいるとまあまあな値段になります。そこで、いい感じに消す作業をしたので、その時の作業内容をまとめてみました。
※AMIを消す処理は、正確には登録解除ですが、ここでは面倒なので削除というワードを使っています。

この作業をする上での前提条件

  • インスタンスに紐付いていないAMIは消してよい
  • AMIに紐付いていないスナップショットでも必要なものはある
  • 消すとしてもAMIは保険として、一週間は残したい
  • 今後もPackerでAMIを作成する
  • トライアンドエラーでAMIを何度か作り直すことがある
  • AutoScallingの起動設定に使うAMIはPackerで作っている
  • AutoScallingの起動設定に使っているAMIは消してはいけない
  • スナップショットはAMIに紐付いていると消せない(これはスナップショットの仕様)

他にも細々としたやつはありますが、ざっくり書くと上のような感じ。

Packerの機能でどうにか出来ない?

出来ればPackerで備わっている機能でどうにかしたいよねー。わかるー。ってことで探しました。それっぽいコマンドは見つかりました。

Amazon EBS - Builders - Packer by HashiCorp

  • force_deregister (boolean) - Force Packer to first deregister an existing AMI if one with the same name already exists. Default false.
  • force_delete_snapshot (boolean) - Force Packer to delete snapshots associated with AMIs, which have been deregistered by force_deregister. Default false.

google翻訳にかけると

  • force_deregister(boolean) - 同じ名前の既存のAMIが既に存在する場合、Packerが既存のAMIを最初に登録解除するように強制します。 デフォルトはfalseです。
  • force_delete_snapshot(boolean) - Force_deregisterによって登録解除されたAMIに関連付けられたスナップショットを削除するようにPackerに指示します。 デフォルトはfalseです。

一瞬使えそうと思ったのですが、AMIを新規に作成した時に、必ず正しいとも限らないので、せめて何世代か前のAMIは残しておきたいとか、あとはAutoScallingの起動設定に使っているAMIが消されると色々面倒。そこを自動で頑張るとかも考えたけどコスト高いのでやめ。

Packerのプラグイン

github.com

もしかしたら、Packerプラグインで何か良いのあるか!?って考えていたらありました。最高。このプラグインは、AMIを作成した際に、何世代か前のAMIを自動で削除してくれるというもの。良さそうだ!ってなりましたが・・・。 機能そのものは問題なく動き、素晴らしいものでした。しかしながら、トライアンドエラーとか繰り返している内に、意図せず消えてほしくない世代のものが消えてしまうかもしれないという点がああああというのと、容量食っているスナップショットをどうにかしたい問題もあり、使うのをやめました。

シェルスクリプトでどうにかするか

結局ここに落ち着いてぐぬぬというお気持ちなんですが、Jenkinsのジョブで定期実行するシェルスクリプトを作成しました。(結局それかいってやつ)

ここ、一旦、前提条件をどうやってクリアするかを考えた内容を雑に書く。

インスタンスに紐付いていないAMIを消す

消そうとしているAMI IDが動いているインスタンスに紐付いていないかをチェックすれば良い。

$ aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" "Name=image-id,Values=hoge"

AutoScallingに使っているAMIは消してはいけない

これは、LaunchConfigurationに紐付いているAMI IDを見れば言いわけですが、弊社では使っているAutoScallingは必ず1つ以上のインスタンスが動いていたので、つまり、上のインスタンスに紐付いているかチェックでカバーが出来る。

スナップショットはAMIに紐付いていると消せない

これは、弊社の前提条件ではなく、スナップショットの前提としてあるので、AMIを先に掃除しないと、スナップショットのお掃除は出来ない。

消すとしてもAMIは保険として、一週間は残したい

これ余裕じゃーんと思ったのですが、describe-images には日付フィルタがなかったので、jqコマンドでゴリゴリ頑張ることになった。

delete_date_range=2016-06-12
aws ec2 describe-images --owner self | \
  jq -r ".Images[] |  select(.CreationDate < \"$delete_date_range\")" | \
  jq -r ".ImageId" | tee ami-id.txt

AMIに紐付いていないスナップショットでも必要なものはある

これが地味に面倒な前提条件としてあった。解決策は実に雑ではあるけど、Packerが作成したスナップショットには何も設定していないと、説明の部分に Created by CreateImage(hogehoge) のような内容があり、これが書いてある=Packerが作ったものと判定することにした。
逆に言うと、これから導入を考えている人は、Packerが作ったものと断定出来るTagとかを付与するのが良さそう。

$ aws ec2 describe-snapshots  --owner self --filter "Name=description,Values=Created by CreateImage(*"
  • AutoScallingの起動設定に使うAMIはPackerで作っている
  • 今後もPackerでAMIを作成する

上記は、ここまでの前提条件クリアすればあまり気にしなくて良い事案。

出来たシェルスクリプト

日本語を豊富に使っているけど、まあ良いではないか。tee の部分とかはどっちでも良かったけど、一応出した感じになっている。 以下はジェンキンスのジョブで定期実行しています。また、スクリプトは同一ジョブ内でstepを分けたような感じで管理しています。 パラメーターを色々切り替えれたりとか考えましたが、あまり頑張らない感じで完成とした。

#!/bin/sh
# 動いているインスタンスに紐付きがないかつ、作成から1週間以上のAMIを削除する
delete_day_limit=7
delete_date_range=`date -d "$delete_day_limit day ago" '+%Y-%m-%d'`

echo "AMI ID取得"
aws ec2 describe-images --owner self | \
  jq -r ".Images[] |  select(.CreationDate < \"$delete_date_range\")" | \
  jq -r ".ImageId" | tee ami-id.txt

for deregister_ami_id in `cat ami-id.txt`; do
  if [ `aws ec2 describe-instances --filters "Name=instance-state-name,Values=running" "Name=image-id,Values=$deregister_ami_id" --output=text | wc -l` -eq 0 ]; then
    aws ec2 deregister-image --image-id $deregister_ami_id
    echo "AMI ID = $deregister_ami_id は登録解除しました 稼働しているインスタンスに紐付きがないため"
  else
    echo "AMI ID = $deregister_ami_id はスキップしました 稼働しているインスタンスと紐付きがあったため"
  fi
done
#!/bin/sh
# Packerが作成したAMIとの紐付きがないスナップショットを削除
echo "\nPackerが作成したSnapshot ID取得"
aws ec2 describe-snapshots  --owner self --filter "Name=description,Values=Created by CreateImage(*" | \
  jq -r '.Snapshots[].SnapshotId' | \
  tee snapshot_id.txt

for delete_snapshot_id in `cat snapshot_id.txt`;do
  if [ `aws ec2 describe-images --filter "Name=block-device-mapping.snapshot-id,Values=$delete_snapshot_id" --output=text | wc -l` -eq 0 ]; then
    aws ec2 delete-snapshot --snapshot-id $delete_snapshot_id
    echo "Snapshot ID = $delete_snapshot_id を削除しました AMIとの紐付きがないため"
  else
    echo "Snapshot ID = $delete_snapshot_id をスキップしました AMIとの紐付きがあるため"
  fi
done

結果

キレイさっぱり消えて、そこそこの月々の費用を削減出来た。
消す作業大変だけど、定期的にやっていこう。

ていうか、jq コマンドすごすぎじゃない?

PackerでAWS AMIを自動で作成する

AWS AMIの管理は面倒

AWSでEC2(IaaS)使ったアプリケーションを構築する時に、AMIの管理が地味に面倒です。例えば、AWS側から提供されているAMIをそのまま使うと、OSがデフォルトに近い状態なので、アプリケーションの実行に必要なものを準備する必要があります。

そこで、実行するために必要な準備を完了させているAMIを作成して、それを使ってインスタンスを作成して、アプリケーションコードを反映するだけで、すぐに使える状態にするということをします。

ただ、これをするのが地味に面倒です。やり方とかは以下の記事に詳しくあります。 tikasan.hatenablog.com

ざっくりいうと、出来上がってるインスタンスからスナップショットを作成して、スナップショットからAMIを作成して、AMIを使ってインスタンスを作成するという手順を踏む必要があります。

これらを自動でやってくれるのが、Packerです。

Packerとは

f:id:tikasan0804:20180621205204p:plain

AWSマネジメントコンソールを使わず、AMIの作成の手順を単純化してくれます。また、プロビジョニング機能があったり、並列実行が出来たりするそうです。

アーティファクト

Packerは最終生成物として、アーティファクトを作成します。アーティファクトとは、Packer独自の抽象化されたマシンイメージの概念のことです。
AWSでいうところのAMIやAMI IDや管理情報。 アーティファクトの作成には、以下の機能を使って作成することができます。(番号は実行順序です)

  1. ビルダー(builder)
    マシンイメージの生成。JSONでテンプレートを定義し、自動で良い感じにしてくれる
  2. プロビジョナー(provisioner)
    マシンイメージ内のミドルウェアやアプリケーションのインストールや設定などを行う。ここではシェルスクリプトはもちろんChefなどの構成管理ツールを実行することもできます
  3. ポスト・プロセッサー(post-processor
    ここでは、最終的な生成物をどうするかということをやる(圧縮とかアップロードとか)

実際にやってみる

インストールはここ見ればわかる Install Packer - Getting Started - Packer by HashiCorp

$ touch hello_ami.json
{
  "variables": {
    "aws_access_key": "hoge",
    "aws_secret_key": "fuga"
  },
  "builders": [
    {
      "type": "amazon-ebs",
      "access_key": "{{user `aws_access_key`}}",
      "secret_key": "{{user `aws_secret_key`}}",
      "region": "ap-northeast-1",
      "source_ami": "ami-06128816a1c781a57",
      "instance_type": "t2.micro",
      "ssh_username": "root",
      "ami_name": "hello_ami {{timestamp}}"
    }
  ]
}
❯ packer build hello_ami.json
amazon-ebs output will be in this color.

==> amazon-ebs: Prevalidating AMI Name: hello_ami 1529395142
    amazon-ebs: Found Image ID: ami-06128816a1c781a57
==> amazon-ebs: Creating temporary keypair: packer_5b28b7c6-9716-5682-837a-45051c4baae6
==> amazon-ebs: Creating temporary security group for this instance: packer_5b28b7c7-913b-2b6d-fa7d-f2dcf8a51770
==> amazon-ebs: Authorizing access to port 22 from 0.0.0.0/0 in the temporary security group...
==> amazon-ebs: Launching a source AWS instance...
==> amazon-ebs: Adding tags to source instance
    amazon-ebs: Adding tag: "Name": "Packer Builder"
    amazon-ebs: Instance ID: i-0284843a27b02a39c
==> amazon-ebs: Waiting for instance (i-0284843a27b02a39c) to become ready...
==> amazon-ebs: Waiting for SSH to become available...
==> amazon-ebs: Connected to SSH!
==> amazon-ebs: Stopping the source instance...
    amazon-ebs: Stopping instance, attempt 1
==> amazon-ebs: Waiting for the instance to stop...
==> amazon-ebs: Creating the AMI: hello_ami 1529395142
    amazon-ebs: AMI: ami-02749c4f55b57389c
==> amazon-ebs: Waiting for AMI to become ready...
==> amazon-ebs: Terminating the source AWS instance...
==> amazon-ebs: Cleaning up any extra volumes...
==> amazon-ebs: No volumes to clean up, skipping
==> amazon-ebs: Deleting temporary security group...
==> amazon-ebs: Deleting temporary keypair...
Build 'amazon-ebs' finished.

==> Builds finished. The artifacts of successful builds are:
--> amazon-ebs: AMIs were created:
ap-northeast-1: ami-02749c4f55b57389c

f:id:tikasan0804:20180619171155p:plain AMIが作成できました!便利すぎる!w

f:id:tikasan0804:20180619171442p:plain 作成するために使ったEC2インスタンスは自動的に削除までされています。

www.packer.io とりあえず、雑に作成しましたが、Builderに必要な権限やクレデンシャルの設定方法は公式見れば分かります。

プロビジョニングは以下のような感じで軽く書ける。

{
  "variables": {
    "aws_access_key": "hoge",
    "aws_secret_key": "fuga"
  },
  "builders": [
    {
      "type": "amazon-ebs",
      "access_key": "{{user `aws_access_key`}}",
      "secret_key": "{{user `aws_secret_key`}}",
      "region": "ap-northeast-1",
      "source_ami": "ami-06128816a1c781a57",
      "instance_type": "t2.micro",
      "ssh_username": "root",
      "ami_name": "hello_ami {{timestamp}}"
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "sleep 30",
        "sudo apt-get update",
        "sudo apt-get install -y redis-server"
      ]
    }
  ]
}

sleep30を最初に書く理由は、ちょっと準備出来るまで余裕ほしいという文脈らしい。なるほど。 www.packer.io

AMI作成自動化最高。

毎日目標を立てないから、頑張れる

毎日目標立てると未達になった時だるい

社会人生活を始めると、結果を出さなきゃーとか、自分で立てた目標達成しなきゃーとか色々あると思います。例えば自分は以下のような日々の目標があります。

  • 早寝早起き(22:00 ~ 06:30)
  • 朝勉強(1~2時間くらい)
  • 定時退社

加えて会社での仕事の目標とかもあったり、社会人って大変!(楽しいですけどね)
これらの目標を真面目に毎日達成しようと思うと、以下のようなことがよく起きる。

早寝早起き目標

  1. 早寝早起きするぞおおお
  2. 頑張ろうとした初日に、飲み会の予定入った
  3. 帰宅が遅くなる
  4. 早寝早起き出来なくて萎える

朝勉強

  1. 朝勉強するぞおおおお
  2. 前日のボルダリングでしんどい
  3. 朝起きれないしんどい
  4. 朝勉強出来なくて萎える

こういうことがあったりしたので、毎日目標を持つことをやめました。

一週間くらいの目標にする

毎日目標を立てない代わりに、一週間スパンで目標を立てることで、さっき書いたような、イレギュラーによる目標失敗パターンでいちいち萎えないようにした。どういうことかというと、週の5日間の中で、3日以上は目標達成出来たらいいや、くらいにしておくという単純な話。こうすることで、目標を達成しやすくした。すると、萎えた気持ちが発生しにくくなり、結果的に立てた目標が継続達成出来るようになった。しかも、飲み会やボルダリングを盛大に楽しめるようになり、変なストレスを感じなくなった。

頑張り過ぎようとすると、折れちゃうからバランス大事。習慣になるまでは、ゆるく頑張る方が良さそう。

AWS EC2バックアップ

EC2でよくやるアレ

検証したい時とかによくやるので、自分のためにメモがてらにまとめた。

SnapshotからAMI

f:id:tikasan0804:20180611205512p:plain バックアップしたいインスタンスIDをメモしておく

f:id:tikasan0804:20180611205515p:plain 該当のEBSを見つけて

f:id:tikasan0804:20180611205518p:plain スナップショットを取る

f:id:tikasan0804:20180611205525p:plain 適当な名前をつける(日付とか用途を入れるとgood)

f:id:tikasan0804:20180611210227p:plain 完了画面で出てくるスナップショットのリンクを押すと作ったやつが出てくる

f:id:tikasan0804:20180611205532p:plain スナップショットからAMIを焼く

f:id:tikasan0804:20180611205538p:plain 適宜設定する
Linux AMI 仮想化タイプ - Amazon Elastic Compute Cloud
仮想化タイプについては、HVM(ハードウェアアシストの仮想化)とPV(準仮想化)があるっぽいですが、HVM方式のがパフォーマンス良くて、利用料金が安いみたいです。
ちなみに、EBSのボリューム内容が違ったりするので、PVからHVMに切り替えるとかが出来ないみたいなので、特別PV使いたい理由が無ければHVMが無難っぽい。

f:id:tikasan0804:20180611205544p:plain 作成したAMIからEC2インスタンス作成

EC2から直接AMI

f:id:tikasan0804:20180611205546p:plain インスタンス選んでイメージ作成で後は適宜設定する

スナップショットからAMI焼くべき?

特別理由が無ければ、スナップショットからAMIを焼く方がいいっぽい。

何故か?という理由とかは上のリンクで大体説明してくれている。この記事では、スナップショットとAMIの違いについて簡単におさらいしてみる。

  • スナップショット:EBSのある瞬間の状態のコピー
  • AMI:EC2インスタンスを作る時に使うディスクイメージ

EC2インスタンスはディスクボリュームとして、「EBS (Elastic Block Store)」というストレージ・デバイスを使っている。このEBSにスナップショットという、ある瞬間の状態を保存する機能があります。

スナップショットは非常に便利で、2回目以降に作成するスナップショットは増分だけを保存してくれます。よって、複数のスナップショットを作成したとしても増分の保存だけで済みます。ちなみに、このスナップショットは、AWSのS3に保存されているので、安心感はある。(イレブン・ナインの堅牢性)

AMIはスナップショットのような増分バックアップとかは出来ません。まるごとイメージにして焼くので、容量的にあまりおいしくないです。ただ、上に貼ったリンクのように、特定の条件下では威力を発揮するようです。

注意点

  • スナップショットは、EC2インスタンスが稼働中でも作成出来ますが、書き込み途中でスナップショットが取られる可能性がある。(稼働中の場合は、整合性は保証されない)
  • リストア時のセキュリティグループは、デフォルトになるので、適宜設定する必要がある(インスタンス作成時に設定出来る)

AWS備忘録

俺用メモ

業務や勉強で出てきたキーワードやメモを雑にまとめている記事。(随時更新するかも)

VPC

AWS上に好きな構成のネットワークを構築出来る。

使い方
1. VPCのトップへ 2. メニューからVPCを選択する 3. VPCの作成

サブネット

VPCで作成したプライベートなネットワーク空間を、さらにCIDRブロックで分割することが出来る。
10.0.0.6 というCIDRブロックをVPCとして割当たところをさらに /24 の大きさで分割するなど。

10.0.0.0/16(10.0.0.0 ~ 10.0.255.255)
↓ /24 分割
10.0.0.0/24(10.0.0.0 ~ 10.0.0.255)
10.0.1.0/24(10.0.1.0 ~ 10.0.1.255)
色々ある
10.0.254.0/24(10.0.254.0 ~ 10.0.254.255)
10.0.255.0/24(10.0.254.0 ~ 10.0.255.255)

使い方

  1. メニューからサブネット選択
  2. サブネット作成
  3. どこで?とからへん設定する

インターネットゲートウェイ

VPCで作成した領域、サブネットをインターネットに接続する。イメージ的には、自分のネットワークにインターネット回線を引き込む作業。 インターネットゲートウェイを作成して、作成したものにチェックして、VPCにアタッチする。 しかし、このままでは、インターネットに接続が出来ません。具体的にやるべき作業としては、「0.0.0.0/0 の範囲の宛先パケットは、インターネットゲートウェイに転送する」という設定をルートテーブルに設定する。(ルートテーブルについては、別項目で詳しく)ちなみに、0.0.0.0/0 は全てのIPアドレスを指している。つまり、「転送先が何も設定されていないものは、デフォルトの転送先にするという意味になります」これをデフォルトゲートウェイという。

使い方
ルートテーブルにサブネットの追加

  1. ルートテーブル作成
  2. 作成したものを選択
  3. サブネット関連付けを編集
  4. 割り当てしたいサブネット選択する。

ルートを編集(デフォルトゲートウェイ

  1. ルートテーブルを選択
  2. ルートを編集
  3. 別のルートを追加
  4. 0.0.0.0/0とigwから始まるターゲットを設定する

ルートテーブル

TCP/IPのネットワーク機器である「ルータ」が、「宛先IPアドレス」を見て、「もっとも宛先IPあどれすに近い方のネットワーク」へと、パケットを転送して、最終目的地にパケットを到達させてます。この仕組には、ルータが、「どちらのネットワークが、より宛先IPあどれすに近いか」を事前に知っておかないと、うまく機能しません。
実際には、VPCを作った時点で、デフォルトのルートテーブルや、サブネット作成時にデフォルトのルートテーブルが適用されるので、意識しなくてもいい。

確認方法

サブネットから、特定のVPCを選ぶ、ルートテーブルが下に出てきているので、それを押すと、特定のルートテーブル情報が出てくる。下のタブで実際に設定されているルートテーブルが出てくる。

ルートテーブルは以下のような設定を持ちます。

宛先アドレス(ディスティネーション) 流すべきネットワークの入り口となるルーター(ターゲット)

簡単な例
ルータ1が知っているサブネットはCIDR0とCIDR1 ルータ2が知っているサブネットはCIDR2とCIDR3 CIDR0がCIDR3に通信するためには、ルータ1とルータ2を経由する必要がある。

設定する値

ルータ1

ディスティネーション ターゲット
CIDR0 local
CIDR1 local
CIDR2 ルータ2
CIDR3 ルータ2

ルータ2

ディスティネーション ターゲット
CIDR0 ルータ1
CIDR1 ルータ1
CIDR2 local
CIDR3 local

ドメイン名と名前解決

www.example.co.jp -> 54.250.90.112 と解決するやつ

使い方

適当な名前を自動でつける場合

  1. VPC設定ページを開く
  2. 該当のVPCを選択して、アクションを開く
  3. DNSホスト名編集をはいと選ぶ
  4. VPCに紐付いているEC2インスタンのDNSを確認

独自ドメイン名をつける場合

レジストラと呼ばれるドメイン事業者から、利用したいドメイン名を取得する。AWSのコンソールから取得する他にも、JIRSの指定業者から取得も可能。
DNSサーバーを構築するためにRoute 53というサービスが提供されています。これに取得したドメイン名を設定すると独自ドメイン名を利用出来るようになります。ちなみに、このRoute 53を使ってドメイン名を取得することも可能です。

NATゲートウェイ

内側からインターネットは利用したいけど、外部からの接続を避けたい時は、IPマスカレードの機能を利用することで実現出来る。利用ケースとしては、閉じたサブネットからのライブラリとかのインストールは許可したいよねー。けど、外からのアクセスはいらないよねーみたいな。

  1. メニューからNATゲートウェイを選択する
  2. NATゲートウェイの作成
  3. 対象サブネットと適当なElastic IPを割り当てる
  4. ルートテーブルに 0.0.0.0/0 natID を設定。
  5. 閉じたネットワークにいるインスタンスからcurl投げてみる

EC2

EC2を使うことで、仮想サーバーを作成することが出来る。この仮想サーバーのことは「インスタンス」と呼びます。このインスタンスには、サブネット内で利用可能な「プライベートIPアドレス」を割り当てます。しかし、インターネットからの接続にプライベートIPアドレスは使えません。そこで出てくるのが「パブリックIPアドレス」です。AWSで割り当てられているIPアドレスぶろっくのうちの適当なものが使われます。

使い方

  1. インスタンス
  2. インスタンス作成
    1. インスタンス起動する際に使うイメージファイル(AMI)を選択する
    2. インスタンスタイプの選択
    3. インスタンスの詳細の設定 VPCの設定とかする(どこのVPCで?どこのサブネットで?IPアドレスはなに?とかはここらへん)
    4. ストレージの追加
    5. タグの追加 なんか色々ある NameでHogeとかにすると名前つけれる
    6. セキュリティグループの設定 インスタンスにセキュリティ設定する機能
    7. 確認と作成 この時にキーペア作成が出来る

インスタンスの停止と再開について

停止すると課金対象から外れ、コストを下げることが出来る。しかし、インタウンスが使っているストレージのEBSは容量を確保している間は課金対象となります。もし、完全に課金を止めたい場合は削除すれば良いのですが、一度削除したインスタンスは二度と復活しないので気をつけること。

パブリックIPアドレスを固定化する

ElaticIPというのを使えば出来る。

  1. メニューからElasticIPを選ぶ
  2. 新しいアドレスの割り当て
  3. なんか適当にやって、作成
  4. 確保したIPアドレス選択
  5. アドレス関連付け
  6. インスタンス選ぶ

SSHで接続する

今回はパブリックIPアドレスを使ってアクセスする。(Mac環境で)

$HOME/.ssh/config に以下のノリで設定する

Host ec2
   User ec2-user
   Port 22
   Hostname 13.131.219.17
   IdentityFile ~/.ssh/my-key.pem

以下のようなエラーが出たら、chmod 400 keyとかでいける。内容としては他のユーザーでも読めるよねー。うん。みたいなノリ。

$ ssh ec2
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@         WARNING: UNPROTECTED PRIVATE KEY FILE!          @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for '/Users/jumpei/.ssh/my-key.pem' are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key "/Users/jumpei/.ssh/my-key.pem": bad permissions
Permission denied (publickey).

$ chmod 400 /Users/jumpei/.ssh/my-key.pem
$ ssh ec2
Last login: Sun Jun 10 12:19:54 2018 from 103.2.241.5

       __|  __|_  )
       _|  (     /   Amazon Linux AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2018.03-release-notes/
10 package(s) needed for security, out of 12 available
Run "sudo yum update" to apply all updates.
[ec2-user@ip-10-0-1-10 ~]$

ファイアウォールで通信を制限する(セキュリティグループ)

パケットフィルタリング
流れるパケットを見て、通過の可否を決める仕組みです。
パケットにはIPアドレスの他、ポート番号やそれに付随するものを見て、通過の可否を決めます。

  • インバウンド 外側から内側へ
  • アウトバウンド 内側から外側へ

使い方
設定内容確認

  1. メニューからセキュリティグループ
  2. 該当のセキュリティグループを選択
  3. インバウンド選択
  4. ルールが見れる

Webサーバの例

  1. インスタンスIPアドレスを取得(パブリックIPとか)
  2. 外部からそのIPアドレスでアクセスしてみる(デフォルトだと22番以外許可されていないから)
  3. インスタンスのセキュリティグループを確認して編集
  4. 今回だと、インバウンドに0.0.0.0/0 に 80番ポートの許可
  5. 2でアクセスしたIPへ再度アクセス

SNS(Simple Notification Service )

SNSはpub/sub メッセージング/モバイル通知サービス。
例えば、YARUO というワードをSNSから送るとこんな感じになる。
これを使って、Lambdaをキックしたり出来る。

{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:85166933371:HelloWorld:2d9efed1-e6ee-4778-9045-d0ad7e0e4481",
      "Sns": {
        "Type": "Notification",
        "MessageId": "4094aa4b-81fe-5b4c-bbb2-f9233432590",
        "TopicArn": "arn:aws:sns:ap-northeast-1:51669633371:HelloWorld",
        "Subject": "None",
        "Message": "YARUO",
        "Timestamp": "2018-06-12T11:37:33.401Z",
        "SignatureVersion": "1",
        "Signature": "T...b6EHMpgAP3D3usg+DKshVqwL1t74Q==",
        "SigningCertUrl": "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-eaea6120e66ea12e88dcd8bcbddca72.pem",
        "UnsubscribeUrl": "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:85169633371:HelloWorld:2defed1-e6ee-4778-9045-d0ad7e0e4481",
        "MessageAttributes": {}
      }
    }
  ]
}

SMS メッセージには、最大で 160 個の ASCII (または 70 個の Unicode) 文字を含めることができます。 メッセージがこの長さを超える場合、Amazon SNS はそれを複数のメッセージとして送信し、それぞれが文字数の制限以内に収められます。 メッセージは単語の途中ではなく、全単語の境界で切り離されます。

使い方

  1. トピックを作成する
  2. 作ったトピックを選んでサブスクリプション作成すると送信先を増やせる
  3. トピックを発行すると、送信先へメッセージが送られる(実際はコードでやることになることが多い)

CloudWatch

lambdaなどの標準出力をログ取ってくれたり、色々ログ取ってくれるやつ。
アラーム設定で、AutoScallingやSNS通知の発行などが出来る。

AlermのSNSの例
ただ、なんのエラーが起きたかは分からないので、巻き取る方法を作る必要がある。

{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:851669633371:HelloWorld:2d9efed1-e6ee-9045-d0ad7e0e4481",
      "Sns": {
        "Type": "Notification",
        "MessageId": "ba186730-a68f-534b-ada6-34a9609753de",
        "TopicArn": "arn:aws:sns:ap-northeast-1:851669633371:HelloWorld",
        "Subject": "ALARM: \"lambda-error\" in Asia Pacific (Tokyo)",
        "Message": "{\"AlarmName\":\"lambda-error\",\"AlarmDescription\":\"lambda-error-desc\",\"AWSAccountId\":\"851669633371\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 out of the last 1 datapoints [1.0 (12/06/18 12:58:00)] was greater than or equal to the threshold (1.0) (minimum 1 datapoint for OK -> ALARM transition).\",\"StateChangeTime\":\"2018-06-12T13:00:07.954\"}",
        "Timestamp": "2018-06-12T13:00:08.005Z",
        "SignatureVersion": "1",
        "Signature": "LB8YEGkR8QazvxNPBxiwzXF4OUtjJIIiJKiDx5c6PGrvRO1/cHMdSncdEcxy0w==",
        "SigningCertUrl": "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-e.pem",
        "UnsubscribeUrl": "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:851669633371:HelloWorld:2d9efed1-4778-9045-d0ad7e0e4481",
        "MessageAttributes": {}
      }
    }
  ]
}

firehoseと組み合わせると便利っぽい。
サブスクリプションを使用したログデータのリアルタイム処理 - Amazon CloudWatch ログ

SQS(Simple Queue Service)

シンプルなキューサービス。出来るのは、データ投入・取り出し・削除のみ。 命令を作る側と、命令を受け取る側でいい感じに分けれる。

よく使うコマンド

プロセス確認

ps -ax

ポートの状況見る

sudo lsof -i -n -P

名前解決確認

nslookup hoge.com

分からないを認識して、分からないと言う話

なんとなく知っているは知らない

社会人になって色々出来ないことはあるだろうけど、絶対これだけはやろうって考えていたのが、「分からないを認識して、分からないと言う」でした。 というのも、学生時代やりがちだったのが、「なんとなく知ってる」話を、「あーなるほど完全に理解したわ(知ってるだけ)」みたいなノリで、話を進めていた。それのせいで、痛い目にあったというよりは、ある会社のインターンのメンターさんに、「分からないを分かった風にしているのがだめ」と言われた一言で、自分が分かった風な気持ちになっていることに、気付かされた経験があり、そこからは、一瞬でも分からないことがあれば聞いてみる。とりあえず、自分の認識を言ってみるをするようにした。

当時の自分は、「知っているけど、よく知らない」を「分からないこと」という認識が出来ていなかった。悪気なく、「あー知ってる」と思ってやり過ごしていました。その「ん?」の感情に無頓着だったせいで、たくさんの学びを失ってきたと、その時に気付かされ、それからは分からないを認識するようになりました。

それからは、「ん?」ってなったら、「それってこういうことですか」「僕の認識ではこういうものだと思っていますが、認識合ってますか?」とか本当に分からない時は、「すいません、○○ってなんですか?」と聞くようにしている。(外部の人と話す時は、また違うのかな?分からないけど)

ちょっとした分からないをそのままにして、仕事を進めると、思わぬ巻き戻しが発生したり、本質を掴めず結局良い設計が出来なかったりと、その場の認識合わせの時間以上に時間を消耗することのが多いので、ガンガン聞いてなんぼだと感じている。分からないをそこで解消すると、次はそれが分かるようになるので、話がスムーズに進むようになる。つまり、さっさと聞いた方がその後の時間の効率化にもなる。

あと、単純に「ん?」は学びの瞬間なので、積極的に注目するべき感情と認識した方がお得。 最近、業務が分からないに溢れているので、改めて分からないことを認識していこうと思ったので、記事にしてみた。

インフラのボトルネックについて知る

インフラのボトルネックを理解する

コードはもちろん、リリースしてから安定して動かせるように面倒を見るまでが仕事というのが、弊社の開発スタイルなので、そこで最近学んだことについて、文献や自分の実体験からボトルネックに関する考え方をまとめてみた。

CPUボトルネック

CPU使用率に対する基本的な考え

CPU使用率が80%から90%をずっと推移している!と聞くと、自分のPCの感覚だと、「やばそう」という感覚に陥りますが、インフラにおいての使用率はそうとも限りません。

  • CPU使用率高い: うまくリソースを使い切っている
  • CPU使用率低い: オーバースペック

ただ、高いCPU使用率にも許容出来る度合いがあったりもするので、そこらへんの判断軸などを踏まえて、まとめてみる。

現実世界の例

CPU使用率が高い状態というのは、実世界に置き換えると、店員がみな忙しく働いているという状態です。利用者からすればオーダーさえ滞りなく通れば文句はなく、経営者もちょうどよい人件費でお店を回せているので、無駄がないと言えます。

システムの例

CPUコアが4つ(店員が4人みたいな)あったとします。それらが全てフル稼働状態だったとします。OSから見た時にCPUの使用率は100%と出ますが、これはネガティブな話ではなく、その逆で、費用対効果が高い状態であり、良い状態と言えます。なぜなら、この状態は他のレイヤーのスループットが非常に高いからです。もし、CPUがボトルネックであったとしても、利用者視点で満足出来る状態であれば問題にはなりません。

なので、CPU使用率が高すぎる!だから良くない!とはすぐに言えず、ユーザーへのレスポンスタイムやスループットに影響がないか?を確認して判断する必要があります。 もし、CPUがボトルネックだったとしても、なぜそうなっているのか?の根本原因を調べることが大事です。

100%は正義ではない

しかし、100%だから効率的だから良い!とも言えません。サービスは成長をします(調子良ければ)。その時に拡張性が担保されている必要はあります。なぜなら、ある日、突然のアクセス増があった時に死んでしまうからです。AWSにはASGという便利な機能があるため、閾値をうまく設定してやれば、突然死ぬことはありませんが、オートスケールは即時制のあるものではありません。(ホットスタンバイとかもありますが)スケールアウトにかかる時間などを加味した上で、上手にリソースを使えるとお金も心にも優しいインフラになると思います。

では、CPU使用率でもよくない高騰した状態ってなんだろう?というところをまとめていく。

待ち行列ボトルネック

ある店舗で、店員が全員忙しく働いているとします。しかし、それでも利用者があまりにも多すぎて、オーダーが通るまで何分も待ち続けるような状態の店があったとします。これはどうでしょう?店員というリソースは完全に使い切っていますが、利用者が明らかにストレスを感じる状況は健全な状態とは言えません。

CPU使用率でも同じことが言えます。処理の方式には待ち行列というものがあります。これはレジの順番待ちのようなものをイメージするとわかりやすいです。CPUの性能に対して、処理待ちのプロセスの質にもよりますが、数が多すぎるという状況になっとします。待ち行列は時間をかければいつか終わりますが、CPUのスループットよりもユーザーからのリクエスト数の方が多いと終わらなくなります。

実店舗などでは、「業務連絡します。3番レジお願いします」などをして、レジを新しくオープンするなどが似たような話です。

システムでは、CPUのコア数を増やして、スループットを高くする。または、プロセスの1処理あたりの時間を短くするなども考えられます。他にはサーバー台数を増やして、並列処理をするようにしたりもできます。

CPUのコア数を増やしたり、サーバー台数を増やしたりするのを、「スケールアウト」と呼んでいます。よく出てくるので覚えておくと話が分かるようになります。

レスポンスボトルネック

スループット問題を解決しても、レスポンス問題が解決するとは限りません。 例えば、ある店舗でのオーダーが、レジ以外にも、商品を作ったり、実際に届けるまでの工程がそもそも時間がかかっていると、店員がどれだけ居ても仕事が回りません。そこで、処理能力そのものをアップさせるという対応を取ることもあります。それが「スケールアップ」と呼ばれているもので、実際には難しいかもしれませんが、店員の処理速度が2倍になれば、処理時間は半分になるので、レスポンスまでの時間が半分になるかもしれません。

処理能力を上げる

CPUにおいては、これをクロック数が先程の店員の処理速度に相当します。CPUのクロック数の単位は「ヘルツ」であり、これは1秒あたりの処理命令数を示しています。 このクロック数を上げることで、一定の効果は得られますが、これには限界があります。どういうことかというと、例えば、店員の一人あたりの単価をアップするよりも(スケールアップする間のダウンタイムは?とかがあったり)、レジの台数を単純に増やすことの方が容易に出来るからです。(拡張性があれば増やすことは簡単なはず) 最近では、スケールアップもオンプレではなく、便利なクラウドがあるので、昔よりは容易なことですが、スケールアップをしたとしても、処理能力を何十倍にも上げるということは難しいでしょう。なによりも、最近のCPUはクロック数にはすごい差があるわけではないので、あまり効果は期待できないケースが多いかもしれません。

並列で処理をする

処理能力はそのままで、処理そのものを分割し、複数のCPUコアに同時処理させる。店員一人が全ての仕事をやるのではなく、それぞれの担当分野を分けるなどです。これで、同時に一人の利用者に対して、複数の店員がいろいろな処理を同時に進めることで、レスポンス時間を短縮するというアプローチです。

この並列で処理するというのは、「並列」、「マルチプロセス」、「マルチスレッド」を使って、一つの処理に対して、CPUコアを複数使うことで、レスポンスタイムの短縮が期待できます。これは、インフラの構成でどうこうというよりも、実際に動いているアプリケーションの改善なので、どういった処理があるのか?などは具体的に掘り下げて、並列化出来るか?をよく検討する必要があります。

CPUの使用率がうまく上がらない

大体のアプリケーションは、CPU使用率が100%に近い状態になることは少なく、ほとんどはCPUから離れたディスクIO、ネットワークIOなどで詰まるケースが多いです。

なぜ、CPU使用率が上がらないかと言うと、例えば、ディスクIOが多い処理があったとします。その場合、システムコールカーネルに命令が行きます。当然その間は、CPUは待ち状態となり、CPUが休んでいる状態なり、CPU使用率が上がりません。ケースにもよりますが、I/O待ちのキューが多くなり、待機するプロセス数が増加したりします。この状態はCPUはぼとるねっくではありません。これはI/O周りのボトルネックとなっている状態です。もし、ディスクのレスポンスに問題がなかったとしても、CPUやメモリ、I/Oなどのリソースがうまく活用されていないと、全体の処理が遅くなることがあります。 

こういった問題の解決には、2つのアプローチがあります。 一つは、同期I/O命令をスレッドごとに命令して、並列実行することで、CPU使用率もI/Oの負荷も上げる。もう一つは、I/Oを非同期化することで、I/O処理の完了待たずに次に進めていく。そうすることで、CPU処理とI/O処理を並行して進めることで、リソースをうまく使う。

メモリボトルネック

僕がやっていた仕事では、まだ遭遇したことがありませんが、いくつかよくあるボトルネックをまとめてみました。

ここでいうメモリとは、「補助記憶装置」ではなく、「主記憶装置」と呼ばれるRAMのことを指しています。 メモリは、CPUの計算結果などを記憶するものです。アクセス速度は補助記憶装置としてよく出てくるHDDやSSDなどに比べると段違いに速いです。全てを主記憶装置と呼ばれるメモリにしないのは、メモリは電気が通っていないと書き込まれていたデータが消えてしまう揮発性のメモリだからです。

スワップによるアクセス速度の低下

メモリに関連して起きる問題は、基本的にメモリ自身に問題が発生するというよりは、周りにしわ寄せが来る印象です。よくあるのがスワップの大量発生によるアクセス速度の低下による全体の処理性能が落ち込む減少です。

スワップとは、メモリの容量が足りなくなった時に、メモリの内容をハードディスクなどの補助記憶装置に移す機能のことです。これによってどういうことが起きるかというと、先にも書いた通り、HDDなどのアクセス速度はメモリと比べると、かなり遅いです。最近ではSSDなどの高速アクセス可能な補助記憶装置も出てきていますが、それでもメモリの方が速いです。 こうなると、発生する問題としては、アクセス時間が長くなり、結果的に処理完了時間が長くなります。

サーバーの状態を見ると、CPU使用率がかなり余裕で、メモリは100%近く使っているといった感じになります。なぜなら、CPUの待ち時間が長くなるので、使用効率が悪くなるからです。こうなると本来の力が発揮できなくなっていると言えます。 メモリがスワップされているかは、freeコマンドを使うと調査ができます。 もし、スワップが大量発生している場合は、メモリリークしているものがないか?とか、メモリに大きなサイズのものを載せすぎでは?などが考えられます。

Linux のメモリー管理(メモリ−が足りない?,メモリーリークの検出/防止)(Kodama's tips page)

その他

  • GC大量発生によるCPU高騰
  • キャッシュメモリアクセス競合によるアクセス速度の低下(まあ、あんま気にすることはない)

書いてて疲れたので、次に行きます。

ディスクI/Oボトルネック

ディスクI/Oは、今までのメモリやCPUに比べると非常に遅いものになります。最近では、SSDなどの高速なディスクもありますが、それでも遅いです。 ディスク周りがボトルネックになるケースは、CPUの使い方を工夫するだけでは、効果があまり上がりません。I/Oの効率が上がるようにするか、I/Oを減らす工夫が必要です。

DBサーバー

ディスクI/Oが発生する先として、以下のような種類のものが、よく出てきます。

  • ローカルストレージ: サーバー内部のディスク
  • SAN(Storage Area Network)
  • NAS(Network Attached Storage)

そして、これらを活用することがあるDBサーバーのボトルネックを見ていきます。

まずは、それぞれを使う場合の通信経路の違いについて

DBサーバー <-> SANスイッチ <-> SANストレージ DBサーバー <-> ネットワークスイッチ <-> NASストレージ DBサーバー <-> 内部ディスク

アプリケーションから見ると、その先で何が使われているかなどを気にする必要はありません。OSカーネル側でリクエストを必要なプロトコルに変換し、ディスクやストレージにリクエストを送ります。確かに動作させる側としては、特に違いを意識する必要はありませんが、インフラを見る側の人間だとこの違いを知る必要があります。

まず、性能の違いについて、これはある程度、前提条件を設定させてもらいます。例えば、ローカルディスクが3~4のディスクでRAID構成を組んでいて、キャッシュにはサーバー上のOSのメモリが利用されているとします。 それに対して、ストレージは何十という単位でディスクを配置してあり、更にキャッシュ専用のメモリ領域が容易されています。ディスクは数によってスループットが向上しますので、外部ストレージのがスループットの面では高い能力を発揮すると言えます。(多くの場合は)

スループットは、同じ領域え利用しているユーザーが多ければ多いほどに低下します。しかし、逆にローカルディスク側はディスクを専有できている点では良いのですが、他のアプリケーション領域と共有している可能性があります。OSのストレージに対しる設定に依存するので、気をつけるべきです。

レスポンスという点では、基本的には近い方が速いので、単体のレスポンスはローカルディスクが最速になります。なので、この差を埋めるために、外部ストレージでは、メモリ領域の活用やデータのキャッシュを効率よく使うなどをして、レスポンス改善に努めています。

今回はスループットやレスポンスという点だけで、ストレージを見ましたけど、実際は対象外性なども入れて考えると、もっと考えるべきポイントが増えてきます。また、さらっと書きましたけど、導入コストの問題を考えたりすると、また面倒だったり・・・(最近はクラウドが便利なので考えること減りましたがw)

HDDやSSDの違いについて知ると、また面白いので良い記事を紹介しておきます。

HDD と SSD の性能とは

ネットワークI/Oボトルネック

ネットワークI/Oのボトルネックはレスポンスタイムのオーバーヘッドに大きく影響します。レスポンス時間の改善をすることは難しいため、そもそも、I/Oが発生する回数を減らすアプローチが有効です。

通信プロセスボトルネック

ネットワーク回線において、帯域というのが重視されることが多いです。帯域が大きいと高速であるわけでないです。 まず、前提として、1つのプロセスで処理をしている場合、高スループットを実現することは難しいです。それは、「データ転送」「通信結果の確認」と言ったやり取りが発生するからです。もし、帯域を使い切ったような通信を行いたい場合、処理を多重化し、並列化する必要があります。そうすることで、通信量が増え、より上限に近いスループットが実現できます。また、そのやり取りに使っているデータを圧縮して、転送量をへらすというのも良いアプローチにはなりますが、圧縮や解凍はCPUオーバーヘッドとトレードオフになります。

ネットワーク経由のボトルネック

ネットワークで起きているボトルネックは目には見えにくいです。実際に処理をしているAPサーバーやDBサーバーなどは、レスポンスタイムが長くなった原因が読み取れません。逆に言うと、特に問題が見えない場合は、ネットワーク系で問題が起きているとも考えられます。

例えば、デフォルトゲートウェイがさばけるトラフィックの限界を迎えていた場合、ここがボトルネックとなり、全体の処理効率がある一定のラインで止まってしまうでしょう。

アプリケーションボトルネック

これまでのインフラの「スケールアップ」「スケールアウト」を使うことで、増強や分散が可能とは書きましたが、そもそもアプリケーション側がそれに対応していないとボトルネックになることがあります。

データ更新ボトルネック

特定のデータに依存した処理に対するボトルネック。 どういうことかというと、あるAというリソースにデータ更新をしにいってるプロセスがあるとします。同じAに対するデータ更新したいプロセスが複数あったとしても、同時に更新することは出来ないので、処理待ちが発生します。そのため、どれだけ処理スピードが上がったとしても、データ更新部分でボトルネックとなります。

解決策はいくつかあって、一つは値をキャッシュ化して、DBサーバーへ問い合わせをせずに内部である程度完結させることで、処理効率を上げる。しかし、これだと根本的な解決にはなりません。 一つは毎度DBに問い合わせることをやめて、RDB以外の場所にRDBに適用するべき内容をためておき、それらを精査して、一定タイミングで適用をしていく(結果整合性)。これではデータのリアルタイム性は失われる可能性があります。これが許容出来ない場合は、データベースの機能などを使って行を分割して同時処理を可能にすることや、そもそも表そのものを分けたりなどをしたりすることも考えられます。サービスの質的に何が担保されている必要があるかによってアプローチは変わってきますので、ここはいろいろある。(雑なまとめ)

他にもいくつかありますが、とりあえず、こんなところにする。