長生村本郷Engineers'Blog

千葉県長生村本郷育ちのエンジニアが書いているブログ

WAF+CloudFront でリファラチェック (直リンク禁止)

概要

AWS WAF (Web Application Firewall) を利用し Cloudfront でのリファラ制御を実装しましたのでそのまとめです。

私は直リンク禁止対策として導入しました。

f:id:kenzo0107:20171005180531p:plain

以下手順になります。

Go to AWS WAF ボタンクリック

サービス > WAF & Shield と辿り Go to AWS WAF クリック

f:id:kenzo0107:20171005174955p:plain

Configure Web ACL ボタンクリック

ACL (Access Control List) を設定していきます。

f:id:kenzo0107:20171005180856p:plain

概要確認

特にチェックせず Next ボタンクリック

f:id:kenzo0107:20171005181044p:plain

web ACL 設定

f:id:kenzo0107:20171005181315p:plain

以下、設定項目を設定し、Next ボタンクリック

Item Value
Web ACL name (任意) 例ではCloudfront の CNAME を設定しています。
CloudWatch metric name Web ACL name を入力すると自動で入力される。変更したい場合のみ変更
Region Global(CloudFront) 選択
AWS resource to associate 対象となるCloudfrontを選択する箇所。運用中の Cloudfront を対象とすると場合は後々設定。

条件作成

今回は文字列一致を条件とする為、 String match conditions にて Create condition ボタンクリック

f:id:kenzo0107:20171005181800p:plain

string match condition 作成

f:id:kenzo0107:20171005181926p:plain

以下設定し Add filter ボタンクリック。
複数 filter がある場合、Add filter を繰り返します。

Item Value
Name (任意)
Part of the request to filter on Header
Header Referer
Match type Contains
Transformation Convert to lowercase
Value to match 対象となるドメイン設定


Add filter 後、 Create ボタンクリック。

f:id:kenzo0107:20171005182327p:plain

Next ボタンクリック

追加したもののすぐに反映されていない。 そのまま Next ボタンクリック

f:id:kenzo0107:20171005182446p:plain

ルール作成

Create rule ボタンクリック。

f:id:kenzo0107:20171005182608p:plain

ルールに条件を紐付け

Name, Cloudwatch metric name を設定し
Add conditions で条件を追加します。

その後、Create ボタンクリック。

f:id:kenzo0107:20171005182641p:plain

ルール以外のリクエストのアクセス禁止

やはり Rule は反映されていない。ですが、続けて
Block all requests that don't match any rules をチェックし Review and create ボタンクリック。

※対象のCloudfront に反映させたくない場合は、Cloudfront を選択したリソースを解除する必要があります。
※最後に関連付けができるのでここではするべきではないと思います。

f:id:kenzo0107:20171005182806p:plain

確認ページで入力内容確認後作成

Confirm and create ボタンクリック。

f:id:kenzo0107:20171005183423p:plain

対象の web ACL を編集

WEB ACLs より選択し Edit web ACL ボタンクリック

f:id:kenzo0107:20171005183628p:plain

web ACL 編集

  1. 作成したルールを選択
  2. Add rule to web ACL ボタンクリック
  3. Allow 選択
  4. Update ボタンクリック

f:id:kenzo0107:20171005183720p:plain

Cloudfront 関連付け

Add association ボタンクリック

f:id:kenzo0107:20171005184119p:plain

Web ACL に Cloudfront を関連付け

Resource で 対象の Cloudfront を選択し Add ボタンクリック

f:id:kenzo0107:20171005184153p:plain

以上で数分後 WAF+CloudFront によるリファラチェックが確認できる状態になります。

アクセス確認

自環境では
ローカルの /etc/hosts 修正し対象ドメインから Cloudfront CNAME へのリンクを貼って確認しました。

Cloudfront CNAME ドメインでのリソースを直接アクセスすると
以下の様な エラーページが表示されることが確認できました。

f:id:kenzo0107:20171005184505p:plain

もう少しユーザフレンドリーに

上記のエラーページは Cloudfront > Error Pages で Create Custom Error Response で S3 上のパスを指定することでカスタマイズが可能です。

是非サイトコンセプトに合ったエラーページをご用意されるとよりユーザフレンドリーな配信になるかと思います。

以上
ご参考になれば幸いです。

AWS Elasticsearch Service バージョンアップ 2.2 → 5.5

f:id:kenzo0107:20170924222936p:plain

概要

AWS Elasticsearch Service (ES) 2.3 → 5.5 へバージョンアップを実施に際して
以下記事をまとめました。

大まかな流れ

  1. ESバージョン2.3のドメインからSnapshot取得
  2. ESバージョン5.5のドメイン作成
  3. アプリの fluentd の向け先をESバージョン5.5へ変更
  4. ESバージョン5.5のドメインにデータリストア
  5. ESバージョン2.3のドメイン削除

現状バージョン確認

$ curl https://<Elasticsearch 2.3 Endpoint Domain>

{
  "name" : "Jackdaw",
  "cluster_name" : "<Account ID>:xxxxxxxxxx",
  "version" : {
    "number" : "2.3.2",
    "build_hash" : "72aa8010df1a4fc849da359c9c58acba6c4d9518",
    "build_timestamp" : "2016-11-14T15:59:50Z",
    "build_snapshot" : false,
    "lucene_version" : "5.5.0"
  },
  "tagline" : "You Know, for Search"
}

その他、AWS console のクラスターの設定確認

その他クラスターへ設定している情報をメモ

AWS Elasticsearch Service スナップショットを S3 で管理する

IAM role 作成

  • ec2 タイプで作成

f:id:kenzo0107:20170924221853p:plain

  • アクセス権限は特に設定せず 「次のステップ」ボタンクリック

f:id:kenzo0107:20170925113614p:plain

  • ロール名を es-index-backups とし作成

f:id:kenzo0107:20170925113908p:plain

ロールARN arn:aws:iam:::role/es-index-backups で作成されていることが確認できる

f:id:kenzo0107:20170925114402p:plain

  • 信頼関係の編集

f:id:kenzo0107:20170925114635p:plain

Service を es.amazonaws.com に編集

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "es.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

IAM User 作成

  • ユーザ > ユーザ追加 選択

f:id:kenzo0107:20170925114822p:plain



  • ユーザ名 es-index-backup-user とし プログラムによるアクセスにチェックを入れて 次のステップ クリック

f:id:kenzo0107:20170925115200p:plain



  • 特にポリシーをアタッチせず 次のステップ クリック

f:id:kenzo0107:20170925115357p:plain

  • es-index-backup-user を作成し独自ポリシーで先ほど作成した role へのアクセス許可設定をアタッチします。
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "iam:PassRole"
            ],
            "Resource": "arn:aws:iam::<Account ID>:role/es-index-backups"
        }
    ]
}


  • 発行されたアクセスキー、シークレットアクセスキーをメモしておきます。

f:id:kenzo0107:20170925120635p:plain

S3 バケット作成

S3 > バケットを作成する でバケット作成してください。

Elasticsearch にてスナップショットリポジトリ作成

スナップショットを管理するリポジトリを Elasticsearch に作成する必要があります。

Elasticsearch へのアクセス可能なサーバにて以下スクリプト実行します。

  • register_es_repository.py
from boto.connection import AWSAuthConnection

class ESConnection(AWSAuthConnection):

    def __init__(self, region, **kwargs):
        super(ESConnection, self).__init__(**kwargs)
        self._set_auth_region_name(region)
        self._set_auth_service_name("es")

    def _required_auth_capability(self):
        return ['hmac-v4']

if __name__ == "__main__":

    client = ESConnection(
            region='ap-northeast-1',
            host='<Elasticsearch 2.3 Endpoint Domain>',
            aws_access_key_id='<ACCESS KEY ID>',
            aws_secret_access_key='<SECRET ACCESS KEY>', is_secure=False)

    resp = client.make_request(
        method='POST',
        path='/_snapshot/index-backups',
        data='{"type": "s3","settings": { "bucket": "<bucket name>","region": "ap-northeast-1","role_arn": "arn:aws:iam::<Account ID>:role/es-index-backups"}}'
    )
    body = resp.read()
    print body
$ chmod +x register_es_repository.py

$ python register_es_repository.py

// 成功
{"acknowledged":true}

リポジトリ登録完了しました。

Snapshot 取得

snapshot 名を 20170926 とします。

$ curl -XPUT "https://<Elasticsearch 2.3 Endpoint Domain>/_snapshot/index-backups/20170926"

// 成功
{"accepted":true}

Snapshot 一覧

20170926 という snapshot 名で取得したことが確認できます。

$ curl -s -XGET "https://<Elasticsearch 2.3 Endpoint Domain>/_snapshot/index-backups/_all" | jq .
{
  "snapshots": [
    {
      "snapshot": "20170926",
      "version_id": 2030299,
      "version": "2.3.2",
      "indices": [
        "nginx-access-2017.09.09",
        "nginx-access-2017.09.07",
        "nginx-access-2017.09.08",
        "nginx-error-2017.08.24",
        "nginx-error-2017.08.23",
        ".kibana-4",
...
      ],
      "state": "IN_PROGRESS",
      "start_time": "2017-09-26T03:58:51.040Z",
      "start_time_in_millis": 1506398331040,
      "failures": [],
      "shards": {
        "total": 0,
        "failed": 0,
        "successful": 0
      }
    }
  ]
}

Snapshot 削除

スナップショット 20170926 を削除する場合、DELETE メソッドを実行します。

$ curl -XDELETE https://<Elasticsearch 2.3 Endpoint Domain>/_snapshot/index-backups/20170926

// 成功
{"acknowledged":true}

S3 確認

以下が作成されているのがわかります。

  • indices/*
  • meta-*
  • snap-*

はじめ meta- が作成できたら完了なのかなと思いきや
snap-
も作られるまで待つ必要がありました。

  • CLI 上でスナップショット完了確認した方が確実です。
$ curl -s -GET https://<Elasticsearch 2.3 Endpoint Domain>/_snapshot/index-backups/20170926

...
      "state": "SUCCESS",
...
...

Elasticsearch 5.5 Service 新規ドメイン作成

Elasticsearch 2.3 の設定に倣って作成します。

リポジトリ作成

  • register_es55_repository.py

register_es_repository.py の host 部分を新規ドメインに修正します。

from boto.connection import AWSAuthConnection

class ESConnection(AWSAuthConnection):

    def __init__(self, region, **kwargs):
        super(ESConnection, self).__init__(**kwargs)
        self._set_auth_region_name(region)
        self._set_auth_service_name("es")

    def _required_auth_capability(self):
        return ['hmac-v4']

if __name__ == "__main__":

    client = ESConnection(
            region='ap-northeast-1',
            host='<Elasticsearch 5.5 Endpoint Domain>',
            aws_access_key_id='<ACCESS KEY ID>',
            aws_secret_access_key='<SECRET ACCESS KEY>', is_secure=False)

    print 'Registering Snapshot Repository'
    resp = client.make_request(
        method='POST',
        path='/_snapshot/index-backups',
        data='{"type": "s3","settings": { "bucket": "<bucket name>","region": "ap-northeast-1","role_arn": "arn:aws:iam::<Account ID>:role/es-index-backups"}}'
    )
    body = resp.read()
    print body
$ chmod +x register_es55_repository.py

$ python register_es55_repository.py

// 成功
{"acknowledged":true}

スナップショットからリストア

20170926 のスナップショットをリストアします。

$ curl -XPOST "https://<Elasticsearch 5.5 Endpoint Domain>/_snapshot/index-backups/20170926/_restore"

// 成功
{"accepted": true}

リストア確認

$ curl -XGET "https://<Elasticsearch 5.5 Endpoint Domain>/_cat/indices"

スナップショットに失敗するケース

  • .kibana index が既に存在しており、リストアできない。
{
    "error":{
        "root_cause":[
            {
                "type":"snapshot_restore_exception",
                "reason":"[index-backups:20170926/Hc4rLIoARCWqpyJXeP7edw] cannot restore index [.kibana] because it's open"
            }
        ],
        "type":"snapshot_restore_exception",
        "reason":"[index-backups:20170926/Hc4rLIoARCWqpyJXeP7edw] cannot restore index [.kibana] because it's open"
    },
    "status":500
}

対応策

curl -XPOST https://<Elasticsearch 5.5 Endpoint Domain>/_snapshot/index-backups/20170926/_restore -d '{
    "indices": "nginx-*"
}' | jq .

indices を用い、スナップショット内のインデックスの中からマッチする正規表現のみをリストアできます。

自身ではこの様な解決法を実施し回避できました。
その他良い方法があれば御指南いただけますと幸いです。

ちなみに

f:id:kenzo0107:20171002144856p:plain

Terraform で ES 2.2 → 5.5 バージョンアップを実施した所
1時間以上経過してようやくアップデートが完了しました。

aws_elasticsearch_domain.elasticsearch: Still destroying... (ID: arn:aws:es:ap-northeast-1:xxxxxxxxxxxx:domain/***, 59m11s elapsed)
aws_elasticsearch_domain.elasticsearch: Still destroying... (ID: arn:aws:es:ap-northeast-1:xxxxxxxxxxxx:domain/***, 59m21s elapsed)
aws_elasticsearch_domain.elasticsearch: Still destroying... (ID: arn:aws:es:ap-northeast-1:xxxxxxxxxxxx:domain/***, 59m31s elapsed)
aws_elasticsearch_domain.elasticsearch: Destruction complete after 59m41s

これは辛い (>_<)

Terraform で管理している場合、
スナップショットを取得したら aws console 上でドメイン削除した方が早い。

ブルーグリーン的に ES 5.5 作成して ES 2.2 から乗り換えて
しばらく運用して問題なければ ES 2.2 を削除する方法が一番確実だなと思いました。

データ分析基盤構築入門[Fluentd,Elasticsearch,Kibanaによるログ収集と可視化]

データ分析基盤構築入門[Fluentd,Elasticsearch,Kibanaによるログ収集と可視化]

高速スケーラブル検索エンジン ElasticSearch Server (アスキー書籍)

高速スケーラブル検索エンジン ElasticSearch Server (アスキー書籍)

  • 作者: Rafal Kuc (lにストローク符号、cにアクサン・テギュ付く),Marek Rogozinski (nにアクサン・テギュ付く)
  • 出版社/メーカー: KADOKAWA / アスキー・メディアワークス
  • 発売日: 2014/03/25
  • メディア: Kindle版
  • この商品を含むブログ (3件) を見る

docker-comopse で Rails 5 (Puma) + Nginx + Mysql 構築 on Vagrant(Ubuntu)

f:id:kenzo0107:20170708204516p:plain

自身の Rails 開発環境の雛形として利用している docker-compose です。

github.com

以下設定手順です。

Vagrant 起動

macOS%$ git clone https://github.com/kenzo0107/vagrant-docker
macOS%$ cd ./vagrant-docker/
macOS%$ vagrant up
macOS%$ vagrant ssh

Rails プロジェクト作成

// on vagrant
vagrant%$ cd /vagrant/rails-puma-nginx-mysql
vagrant%$ docker-compose run --rm web rails new . --force --database=mysql --skip-bundle

puma 設定ファイルセット

vagrant%$ cp puma.rb ./rails/config/
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
plugin :tmp_restart

app_root = File.expand_path("../..", __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

データベース設定ファイルセット

vagrant%$ cp database.yml ./rails/config/
  • ./rails/config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: <%= ENV['MYSQL_ROOT_PASSWORD'] %>  # <--- MYSQL_ROOT_PASSWORD
  host: db # <--- service name

データベース作成

vagrant%$ docker-compose run --rm web rails db:create
Created database 'app_development'
Created database 'app_test'

vagrant%$ docker-compose exec db mysql -u root -p -e'show databases;'
Enter password: (password)
+--------------------+
| Database           |
+--------------------+
| information_schema |
| app_development    | <--- add !
| app_test           | <--- add !
| mysql              |
| performance_schema |
| sys                |
+--------------------+

以上で必要最低限の Rails プロジェクトの準備ができました!

Rails, Nginx, MySQL 全コンテナ起動

vagrant%$ docker-compose up -d

ブラウザより http://192.168.35.101 へアクセスし
Rails トップページが表示されることが確認できます。

f:id:kenzo0107:20170913132149p:plain

総評

docker でコンテナ化しているので Nginx, MySQL 等、
バージョンアップしたい時でもコンテナを置き換えるだけで簡単に使用感を確認できたり
機能を確認できたりと便利です。

これに Elasticsearch + Kibana でログを可視化したり
Mailcatcher でメール送信を確認できるようにしたりと
開発するには十分な状況が用意できます。

是非開発の一助になれば幸いです。

パーフェクト Ruby on Rails

パーフェクト Ruby on Rails

MySQL 一定秒以上 Sleep しているプロセスを一括 kill

メモ

300秒以上 Sleep しているプロセスIDをまとめて表示

$ mysql -h <host> -u <user> -p -e'SELECT GROUP_CONCAT(ID) FROM information_schema.PROCESSLIST WHERE TIME > 300;'

+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| GROUP_CONCAT(ID)                                                                                                                                                                                                                                                                                                                                                                                      |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| 2147,2143,2138,2113,2111,2104,2102,2098,2087,2085,2082,2081,2079,2078,2069,2068,2045,2037,2029,2025,2023,2016,2015,2006,2005,2003,2002,2001,1999,1998,1997,1995,1987,1986,1984,1982,1981,1974,1973,1968,1966,1963,1961,1959,1957,1955,1954,1949,1937,1936,1928,1925,1923,1920,1916,1914,1912,1908,1906,1898,1892,1869,1847,1842,1651,1650,1572,1570,1568,1566,1539,1522,1517,1516,1514,1511,1506,1483 |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

プロセスIDをまとめて一括 kill

$ mysqladmin kill <pid,pid,pid...> -h <host> -u <user> -p 

Vagrant + docker-compose で Rails 5.1.0 (Puma) + Nginx + MySQL 環境構築

f:id:kenzo0107:20170823110755p:plain

概要

簡易的に Rails 環境を構築・開発できる様にすべく構築しました。

こんな時に利用してます。

  • 新規プロジェクト開発
  • 新規 gem, その他ミドルウェアの試験
  • 簡単なモックを作ってディレクターに見せたい時とか

構築手順をまとめました。

環境

Git Clone

macOS%$ git clone https://github.com/kenzo0107/vagrant-docker
macOS%$ cd vagrant-docker
macOS%$ vagrant up
macOS%$ vagrant ssh
vagrant%$ cd /vagrant/rails-puma-nginx-mysql/

Rails プロジェクト作成

// database = mysql
vagrant%$ docker-compose run --rm web rails new . --force --database=mysql --skip-bundle

puma.rb 設定

// backup
vagrant%$ cp ./rails/config/puma.rb ./rails/config/puma.rb.bk
vagrant%$ cp puma.rb ./rails/config/
threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
plugin :tmp_restart

app_root = File.expand_path("../..", __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

データベース接続情報設定

// backup
vagrant%$ cp ./rails/config/database.yml ./rails/config/database.yml.bk
vagrant%$ cp database.yml ./rails/config/
  • ./rails/config/database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: <%= ENV['MYSQL_ROOT_PASSWORD'] %>  # <--- MYSQL_ROOT_PASSWORD
  host: db # <--- service name

DB作成

vagrant%$ docker-compose run --rm web rails db:create
Created database 'app_development'
Created database 'app_test'

vagrant%$ docker-compose exec db mysql -u root -p -e'show databases;'
Enter password: (password)
+--------------------+
| Database           |
+--------------------+
| information_schema |
| app_development    | <--- add !
| app_test           | <--- add !
| mysql              |
| performance_schema |
| sys                |
+--------------------+

Rails 実行

vagrant%$ docker-compose up -d

http://192.168.35.101 にアクセスすると Rails のウェルカムページが表示されます。

f:id:kenzo0107:20170822123732p:plain

rails g

rails g 実行時は基本 one-off container で実行するのが良いです。

例えば以下は articles テーブルを作成、また、関連する controller, view, model を作成します。

vagrant%$ docker-compose run --rm web rails g scaffold article title:string body:text

Gemfile 更新

Gemfile 更新した際はビルドし再起動します。

vagrant%$ docker-compose stop web
vagrant%$ docker-compose build web
vagrant%$ docker-compose up -d web

あとがき

Rack server との接続は一癖ありましたが、そこさえ乗り越えたら
すっと行きました♪

DB は 3306 でオープンしてるので
Mac のローカルから Sequel Pro で接続して確認できます。

これをベースに EFK でログ確認できる様にしたり、
mailcatcher でメール機能を試験できる様にしたりと
何かと便利です。

Docker 有難や♪

Ruby on Rails 5アプリケーションプログラミング

Ruby on Rails 5アプリケーションプログラミング

今更聞けない!CPU, Memory 使用率の見方

f:id:kenzo0107:20170810140724p:plain

気持ちを抑えられずありがちなタイトルを付けました。

サーバ負荷監視時のボトルネックの特定をする為、
実際に手を動かして自分で見て解決するというチュートリアルとして
本記事を参照いただければ何よりです。

サーバに接続し辛い

f:id:kenzo0107:20170810141554p:plain

  • ブラウザからURLを打ち込みサイトにアクセスするもページが表示されない
  • API が timeout する

上記の様な事象が発生した場合は
監視グラフに異変が起きているはずです。

その監視グラフを元に
アクセスしづらくなった徴候のある負荷状況を確認し
ボトルネックを特定していきます。

以下、出来るだけ原理を知る上で CLI を元に話を進めていきます。

サーバにCPU負荷を掛ける

f:id:kenzo0107:20170810142151j:plain

CPU負荷を掛けるツールとしてLinuxのI/OやCPUの負荷とロードアベレージの関係を詳しく見てみるスクリプトを拝借しました。
※ありがとうございます @kunihirotanaka 社長!

  • loadtest.pl
    • arg1: 並列実行するプロセス数
    • arg2: システムコールするプログラムを動作させるか判定
#!/usr/bin/perl

my $nprocs = $ARGV[0] || 1;
for( my $i=0; $i<$nprocs; $i++ ){
    my $pid = fork;
    die $! if( $pid < 0 );
    if( $pid == 0 ){
        while(1){
            if( $ARGV[1] ){
                open(IN, ">/dev/null");
                close(IN);
            }
        }
    }
}
wait;

ロードアベレージを 2 に近づける様にCPU負荷を掛ける

$ chmod +x ./loadtest.pl
$ ./loadtest.pl 2

CPU使用状況確認コマンド

  • リアルタイム監視 → top, vmstat
  • 過去確認 → sar

リアルタイム監視

top で概要確認

$ top
top - 12:12:39 up 592 days, 18:55,  4 users,  load average: 1.97, 1.13, 0.46
Tasks: 125 total,   3 running, 122 sleeping,   0 stopped,   0 zombie
Cpu(s): 99.7%us,  0.2%sy,  0.0%ni,  0.0%id,  0.0%wa,  0.0%hi,  0.0%si,  0.2%st
Mem:   1020052k total,   943132k used,    76920k free,   144136k buffers
Swap:  2097148k total,   466724k used,  1630424k free,   410784k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
29955 mameko_d  20   0 25196  520  184 R 99.9  0.1   4:04.91 loadtest.pl
29956 mameko_d  20   0 25196  516  180 R 99.5  0.1   4:04.86 loadtest.pl
24534 apache    20   0  425m  25m 7480 S  0.3  2.6   1:42.63 httpd
    1 root      20   0 19232  412  224 S  0.0  0.0   0:01.77 init
...
...

上記結果から
- Load Average が上昇している
- %CPU, COMMAND から上昇の原因は loadtest.pl

暫定的な対処としてはこのプロセスを kill することで負荷を止めることができます。

$ kill -9 6528
$ kill -9 6529

処理途中でプロセスを kill してしまい不整合が発生する様な処理の場合は
別途、CPU の増強等を検討する等、状況によりますが対応を検討する必要があります。

グラフで見る

これまで CLI で確認した考察の答え合わせとして確認しましょう。

f:id:kenzo0107:20170810121609p:plain

  • 12:07 辺りから負荷上昇
  • loadavg5 急上昇
  • CPU user 急上昇。 CPU system はそこまで上がっていない。 → アプリケーションのプロセスがCPUを食っている。
  • memory は消費していない

top コマンドの様にどのプロセスが原因かまではグラフからは不明です。
サーバにアクセスして12:07 あたりからのログを調査する等原因を特定していきます。

補足

ちなみに
Apache のモジュールとして PHP を利用している場合は COMMANDhttpd と表示されます。
fluentd は ruby で実行されているので ruby です。

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
12376 apache 20 0 833m 115m 19m S 2.4 3.1 0:03.52 httpd
1455 td-agent 20 0 461m 74m 0 S 1.2 2.0 1098:30 ruby

vmstat で CPU 使用率確認

1秒ごとに出力

$ vmstat 1

procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----  
r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st  
0  0 515396  80756  37120 220320    0    0    12     2    0    0  0  0 100  0  0  
0  0 515396  80756  37120 220320    0    0     0     0  110  200  0  0 100  0  0  
0  0 515396  80756  37120 220320    0    0     0     0  103  205  0  0 100  0  1  
0  0 515396  80632  37120 220320    0    0     0     0  121  212  0  0 99  0  0  
0  0 515396  80632  37120 220320    0    0     0     0  114  216  0  0 100  0  0  
2  0 515328  80648  36944 220432    0    0     0     0 2092  237 100  0  0  0  0  
2  0 515328  80648  36944 220432    0    0     0     0 2071  224 100  0  0  0  0  
2  0 515328  81020  36952 220432    0    0     0     0 2162  381 100  1  0  0  0  
2  0 515328  80896  36952 220432    0    0     0     0 2164  266 100  0  0  0  0  
2  0 515328  80756  36952 220432    0    0     0     0 2139  308 100  0  0  0  0  
2  0 515328  80772  36952 220432    0    0     0     0 2111  237 100  0  0  0  0  
2  0 515328  80772  36952 220432    0    0     0     0 2087  238 100  0  0  0  0  
2  0 515328  80772  36952 220432    0    0     0     0 2077  237 100  0  0  0  0  
2  0 515328  80772  36952 220432    0    0     0     0 2076  232 99  1  0  0  0  
2  0 515328  80772  36952 220432    0    0     0     0 2078  235 100  0  0  0  0  
2  0 515328  80904  36952 220432    0    0     0     0 2081  231 85  0  0  0 15  
0  0 515328  81448  36952 220432    0    0     0     0  267  254  6  0 94  0  0  
0  0 515328  81448  36952 220432    0    0     0     0  151  250  0  0 99  0  0  
0  0 515328  81448  36952 220432    0    0     0     0  230  307  0  0 99  0  0  
0  0 515328  81456  36952 220432    0    0     0     0  123  230  0  0 100  0  0  

上記から以下のことが確認できます。
- ./loadtest.pl 2 を実行中は procs r (実行中プロセス) = 2 となっている
- cpu us が 100%, cpu id が 0%
- cpu id がなくなり、プログラムが 100% CPU を食いつぶしている
- system in (割り込み回数)、system cs (コンテキストスイッチ回数) が増加
- コンテキストスイッチ自体が CPU を食いシステムの負荷を上げている

過去確認

$ sar -u -s 21:00:00 -e 22:10:00 -f /var/log/sa/sa31

21:00:01      runq-sz  plist-sz   ldavg-1   ldavg-5  ldavg-15  
21:10:01            0       200      0.00      0.04      0.07  
21:20:01            0       203      0.00      0.00      0.01  
21:30:01            0       203      0.00      0.00      0.00  
21:40:01            0       211      0.00      0.03      0.00  
21:50:01            0       210      0.65      0.82      0.37  
Average:            0       205      0.13      0.18      0.09  

sar コマンドは過去まで遡って確認できるので便利です。

sar -q 実行結果各項目
Item Explain
runq-sz CPU を実行する為のメモリー内で待機中のカーネルスレッド数。
通常、この値は 2 未満になる。
値が常に 2 より大きい場合は、システムが CPU の限界に到達している可能性がある
plist-sz プロセスリストのプロセスとスレッド数
ldavg-1 過去1分間のロードアベレージ
ldavg-5 過去5分間のロードアベレージ
ldavg-15 過去15分間のロードアベレージ

システムコールを伴うCPU負荷

ロードアベレージを 2 に近づける & システムコールする様にCPU負荷を掛ける

$ ./loadtest.pl 2 1

vmstat で監視

$ vmstat 1

procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----  
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st  
 0  0 597832 886856   3808  34844    0    0    12     2    0    0  0  0 100  0  0  
 1  0 597828 886856   3808  34908    0    0     0     0  134  233  1  0 99  0  0  
 0  0 597788 886684   3944  34876   20    0   156     0  238  307  1  1 96  4  0  
 0  0 597788 886684   3944  34904    0    0     0     0  109  219  0  0 100  0  0  
 2  0 597756 884044   3956  36296   96    0  1500     0 1075 1274 18 26 41 15  0  ← loadtest.pl 2 1 実行開始  
 2  0 597756 884044   3956  36296    0    0     0     0 2080 2265 42 58  0  0  0  
 2  0 597756 884076   3956  36296    0    0     0     0 2083 2458 41 60  0  0  0  
 2  0 597756 884200   3964  36292    0    0     0    32 2103 2458 42 59  0  0  0  
 2  0 597756 884200   3964  36296    0    0     0     0 2079 2588 41 60  0  0  0  
 3  0 597756 883952   3964  36296    0    0     0     0 2080 2209 40 60  0  0  1  
 2  0 597756 884216   3964  36296    0    0     0     0 2085 2395 42 58  0  0  0  
 2  0 597756 884216   3964  36296    0    0     0     0 2061 2399 43 57  0  0  0  
 3  0 597756 884092   3964  36296    0    0     0     0 2061 1991 44 57  0  0  0  
 2  0 597756 884216   3964  36296    0    0     0     0 2059 2333 42 58  0  0  1  
 2  0 597756 884216   3964  36296    0    0     0     0 2058 2211 42 58  0  0  1  
 2  0 597756 884092   3964  36296    0    0     0     0 2058 2461 43 58  0  0  0  
 2  0 597756 883844   3964  36296    0    0     0     0 2059 2641 42 58  0  0  0  
 2  0 597756 884216   3964  36296    0    0     0     0 2158 2715 42 59  0  0  0    ← loadtest.pl 2 1 実行終了  
 0  1 597744 884588   3964  36364   44    0   144     0 1995 2313 37 58  3  2  0  
 0  0 597724 884388   3964  36524  208    0   380     0  173  239  0  1 95  5  0  
 0  0 597724 884388   3964  36568    0    0     0     0  102  196  0  0 100  0  0  
 0  0 597636 884388   3964  36568    0    0     0     0  102  203  0  0 100  0  0  
 0  0 597636 884512   3964  36568    0    0     0     0  104  195  0  0 100  0  1  
  • loadtest.pl からシステムコールが多数実行される為、cpu sy 上昇している。

グラフで見る

f:id:kenzo0107:20170810123318p:plain

  • 12:25 辺りから負荷急上昇
  • loadavg5 急上昇
  • CPU user, system 共に急上昇。 system の割合が多い
    → システムコールを伴うアプリケーションのプロセスがCPUを食っている。
  • memory は消費していない

対応例

  • アプリケーションのCPU使用箇所の特定
    • datadog, NewRelic 等の APM(Aplication Performance Management) 導入しアプリケーションのボトルネック抽出し修正
      • コストこそ掛かりますが非常に有用です
  • CPU増設
  • 対象アプリのプロセスを kill (先ほどの プロセス kill )
    • 例)管理画面で集計処理し、DBに負荷掛けサービスに影響してしまった時に集計処理のプロセスを kill

サーバにメモリ負荷を掛ける

f:id:kenzo0107:20170810142217j:plain

  • memorytest.pl

1秒毎に 20MB 消費する様に設定

#!/usr/bin/perl

while(1){
    sleep 1;
    push @h  , 1 x 1024 x 1024 x 20
}
wait;
  • メモリ負荷実行
$ chmod +x ./memorytest.pl
$ ./memorytest.pl

メモリ使用状況確認コマンド

  • リアルタイム → top -a, free
  • 過去確認 → sar

残メモリ確認

top で概要確認

$ top -a

もしくは top 実行後、 Shift + m

PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
6780 mameko_d 20 0 385m 362m 1212 S 5.3 36.4 0:01.88 memorytest.pl

上記結果から
- %MEM, COMMAND から上昇の原因は memorytest.pl

暫定的な対処としては loadtest.pl と同様、プロセスを kill することで負荷を止めることができます。

$ kill -9 6780

free で残メモリ確認

$ free

             total       used       free     shared    buffers     cached  
Mem:       1020052     908664     111388        572      29764     204492  
-/+ buffers/cache:     674408     345644  
Swap:      2097148     517696    1579452  

何度か実行すると徐々に Mem
- used 上昇
- free 減少
- free 減少に引っ張られて buffers, cached 減少

free 実行結果各項目
  • Mem: 実メモリ
  • Swap: 仮想メモリ
Item Explain
shared プロセス間で共有できる領域用メモリ
buffers buffer に利用されるメモリ
I/Oアクセスする時に、直接I/Oに行くのではなく、キャッシュ経由でアクセスさせる為のメモリ
cached ページキャッシュ用メモリ
アプリで実メモリが必要な際は、 cached のメモリが破棄される

確認観点

上記 free コマンド実行結果から解放されたメモリは

Mem free 111388 kB = 108 MB  

これだけでは 108 MB が残りと思いがちですが
通常、各プロセスにメモリ割り振った残りを buffercache に利用して disk I/O を軽減している為、
buffer + cache も含まれます。

実質、残メモリはどこ見れば良い?

free + buffers + cached
= 111388 + 29764 + 204492 kB = 345644 kB
= 338 MB
= -/+ buffers/cache free

以上から
残メモリの目安は -/+ buffers/cache free 確認 or スラッシングを確認します。

Swap 発生は何を見ればわかる?

vmstat 実行してスラッシングが発生しているか確認

スラッシング確認方法
  • si (swap in from disk), so (swap out to disk) が多発している場合、スラッシングが発生しています
  • so が比較的高い場合、メモリ不足の可能性大
$ vmstat 1

procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----  
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st  
 0  0 593244 211856   3704  34744    0    0     0     0  236  215  4  2 94  0  0  
 1  0 593244 211876   3704  34744    0    0     0     0  144  216  1  0 98  0  0  
 0  0 593244 160664   3704  34744    0    0     0     0  207  220  4  1 96  0  0  
 0  0 593244 109328   3704  34744    0    0     0     0  227  226  4  2 94  0  0  
 0  1 593052  58900   3704  33312    0    0     0     0  515  241  4  2 59 34  0  
 0  1 618460  55144   1224  15220    0 25416     0 25416 39357  528  5 24 34 37  1  ← ここで memorytest.pl 行開始
 0  1 670408  56644   1160  13732    0 51948     0 51948 37384  644  7 22 34 37  1  
 1  1 671048  59256    456  12480    0  640     0   640  182  254  1  1 49 50  0  
 0  0 735892  72040    436  10088    0 64844    96 64844 14559 1044  3 15 67 15  0  
 0  0 786748  71436    436   9596    0 50856   124 50856 13678  745  4 13 69 14  1  
 0  2 830880  63556    436   9504   32 44144   320 44144 15659  636  5 13 54 28  0  
 0  3 849932  48976    436   8652  304 19168  1104 19168 6568  661  6  6 32 55  1  
 0  1 900916  71564    776   8264   88 50992   560 50992 9549  706  1 10 27 62  0  
 0  3 941292  64152   1308   9880   80 40380  2564 40380 10800  622  5 11 29 56  1  
 0  1 993108  69908   1700  10656    0 51820  2024 51852 10208  848  5 12 43 40  1  
 2  0 994168  71536   1700  10428    0 1060     0  1060  216  257  3  1 82 15  0  
 0  0 1045720  71384   1700   9456    0 51552     0 51552 5356  789  2  9 77 12  1  
 0  0 1096468  71468   1332   9012    0 50748     0 50748 15385  857  6 13 72  9  1  
  • bo (block out to device) … ブロックデバイスに送られたブロック
  • bi (block in from device) … ブロックデバイスから受け取ったブロック

グラフで見る

f:id:kenzo0107:20170810125452p:plain

  • 12:35 辺りからメモリ急上昇
  • cached 減、 used 増

対応例

  • プロセスレベルで監視
    • datadog, mackerel, prometheus, zabbix 等監視ツール導入
  • メモリ増設
  • 対象アプリのプロセスを kill (先ほどの プロセス kill )

Disk I/O

I/Oディスク利用状況 確認

$ sar -b -s 13:00:00 -e 14:00:00 -f /var/log/sa/sa31

13:00:01          tps      rtps      wtps   bread/s   bwrtn/s
13:10:01         0.28      0.01      0.27      0.11      2.90
13:20:01         0.28      0.00      0.28      0.05      2.99
13:30:01         0.22      0.00      0.22      0.00      2.30
13:40:01         0.24      0.00      0.24      0.00      2.50
13:50:01         0.23      0.00      0.23      0.03      2.35
Average:         0.25      0.00      0.25      0.04      2.61

sar -b 実行結果項目

Item Explain
tps 1秒あたりの転送 (デバイスに対するIOリクエスト) 数の合計
rtps 1秒あたりの読み込みIOリクエストの回数の合計
wtps 1秒あたりの書き込みIOリクエストの回数の合計
bread/s 1秒あたりの(ブロック単位)読み込みIOリクエストのデータ量の合計
bwrtn/s 1秒あたりの(ブロック単位)書き込みIOリクエストのデータ量の合計

確認観点

  • I/O 待ち増加する原理
プロセスのメモリ消費量増
↓  
キャッシュにメモリが使えなくなる  
↓  
Disk I/O にメモリが使えなくなる  
↓  
Disk I/O 増加  
↓  
I/O 待ちプロセス増加  

対策

  • メモリの使用状況と Swap 確認

まとめ

CPU, Memory 使用率が上昇する原理を知った上で監視をすると
全然グラフの見え方が違うことを実感しました。

本来はグラフから見ることになるかと思います。
その際に top, vmstat, sar を実行した時の数値の変化の仕方をイメージすると
より原因追及に大きな一歩となると思います。

以上 少しでも参考になれば幸いです。

Reference

補足

Swap とは?

カーネルがほぼ利用されていないメモリを検出して排除している状態を表示しています。

  1. メモリの空きを入出力のバッファとキャッシュに使用
  2. さらに活動が少ない部分をメモリから排除して swap へ押し出す
  3. swap からバッファやキャッシュに転用

その為、 swap が発生している、といって慌てない。

Swap を利用する意義

メモリ不足でもメモリの一部をディスクに退避させて計算し続けることができます。
メモリを使い切った様に見せつつもまだ使える仕組みを 仮想メモリ と言います。

スラッシングとは?

実メモリ と Swap のメモリの移動が大量発生し処理が遅延する現象です。

sar コマンドがない場合はインストール

// CentOS
$ yum install -y sysstat

// Ubuntu
$ apt-get install -y sysstat

Go+Revelフレームワーク 非同期でS3へ画像リサイズ/アップロード

備忘録です。

概要

AWS向けのgoライブラリが乱立していて どれ使ったらいい?という感じだったので 本家の launchpad.net/goamz/aws を利用して 実装することにしました。

Controller

  • app/controllers/img.go

gist.github.com

Component

画像アップロード部分をcomponent化しました。

  • app/utility/aws.go

gist.github.com

Views